Render Blocking View
A timeline visualiser of the render-blocking resources that hold up first paint. Pass a flat list of resources tagged with blocking: boolean; the widget stacks one row per resource, tints blocking rows with the accent and non-blocking rows muted, and overlays a paint marker at firstPaintMs (derived from the blocking total when omitted). A summary footer counts blocking vs non-blocking resources and surfaces the total blocking weight.
Preview
Render-blocking resources
Bars sized by weight; the marker is the moment the page can paint.
app.css38.0 KB
vendor.css22.0 KB
Inter18.0 KB
main.js64.0 KB
analytics.js9.00 KB
Blocking3 blocking · 2 non-blocking · 124 KB
Customize
Resource sizes (KB)
38KB
64KB
18KB
Blocking
Display
Installation
npx shadcn@latest add https://craftbits.dev/r/render-blocking-view.jsonUsage
import {
RenderBlockingView,
type RenderBlockingViewResource,
} from "@craft-bits/core";
const resources: RenderBlockingViewResource[] = [
{ id: "app-css", label: "app.css", kind: "css", blocking: true, sizeKb: 38 },
{ id: "main-js", label: "main.js", kind: "script", blocking: true, sizeKb: 64 },
{ id: "inter", label: "Inter", kind: "font", blocking: false, sizeKb: 18 },
{ id: "analytics", label: "analytics.js", kind: "script", blocking: false, sizeKb: 9 },
];
export function Demo() {
return (
<RenderBlockingView
title="Render-blocking resources"
resources={resources}
/>
);
}Pass firstPaintMs explicitly to drive the marker from a step counter or a profiler trace; omit it to let the widget derive a sensible value from the blocking weight.
Anatomy
- Header. Optional
title(rendered with thecb-labelstyle) anddescription. Omit both for a chromeless panel. - Timeline rail. A stack of resource rows. Each row carries a leading kind badge ('css', 'js', 'font'), a horizontal weight bar sized to the largest resource in the set, the visible label, and a tabular size readout.
- Paint marker. A vertical accent rule overlaid on the rail at
firstPaintMs / totalMsof the rail width, with a small caption above it. SethidePaintMarkerto drop it. - Summary strip. Beneath the rail — counts blocking vs non-blocking and prints the total blocking weight. Tints accent when blocking is non-zero, success once everything is non-blocking.
Understanding the component
- Blocking vs non-blocking. The widget reads
blockingstraight off each resource — no internal scoring. Blocking rows pick up the accent tone for the badge and bar; non-blocking rows render with the muted palette. - Bar scale. Each bar's width is
sizeKb / maxSizeKbof the rail, with a 12% floor so even tiny resources read as a bar. - Paint marker derivation. When
firstPaintMsis omitted, the marker lands at80ms + 6ms per blocking KB, clamped into the[80, totalMs - 40]window. PassfirstPaintMsto override. - Motion. Each bar grows from zero width with
SPRINGS.smooth, and the paint marker slides in from the left with the same spring. Animations short-circuit underprefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
resources | RenderBlockingViewResource[] | required | Ordered list of resources on the timeline. |
title | ReactNode | — | Optional heading above the panel. |
description | ReactNode | — | Optional sub-headline under the title. |
firstPaintMs | number | derived | Milliseconds at which the paint marker lands. |
totalMs | number | 2000 | Total timeline span in milliseconds. |
unit | string | 'KB' | Display unit suffix for size values. |
hidePaintMarker | boolean | false | Hide the paint marker overlay. |
hideSizes | boolean | false | Hide the per-row size caption. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
paintLabel | ReactNode | derived | Override the paint marker caption. |
className | string | — | Merged onto the root via cn(). |
RenderBlockingViewResource
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. |
label | ReactNode | Visible row label. |
kind | 'css' | 'script' | 'font' | Resource bucket; drives the leading badge. |
blocking | boolean | true when the resource blocks first paint. |
sizeKb | number | Approximate size; drives the bar width and caption. |
Accessibility
- The wrapper is a
<section>withdata-cb-edu="render-blocking-view"anddata-cb-has-blocking="true | false"mirroring the blocking count. - The timeline rail carries an
aria-labeldescribing it as the render-blocking timeline; the paint marker itself isaria-hiddenbecause the marker text duplicates information that is also conveyed in the summary strip. - Every resource row carries an
aria-labelof the form "Blocking css 38 KB" so screen readers convey the bucket, the blocking flag, and the size without depending on colour. - The summary strip lives in an
aria-live="polite"region so the counts update audibly when the consumer mutates the resource list. - Animations short-circuit under
prefers-reduced-motion.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/perf-css/ui/RenderBlockingView.tsx). The original was bound to thecss-perf-simulatorengine — a fixed three-mode radiogroup ('blocking | deferred | inline') that read aRenderBlockingTimeline(paint marker, FCP label, "styled" boolean) computed from a hardcoded 180 KB stylesheet. This rewrite drops the mode toggle and the simulator coupling and generalises the surface to a list of{ id, label, kind, blocking, sizeKb }resources so the view can model any render-blocking scenario, withfirstPaintMsas an optional override.