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.json

Usage

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 the cb-label style) and description. 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 / totalMs of the rail width, with a small caption above it. Set hidePaintMarker to 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

  1. Blocking vs non-blocking. The widget reads blocking straight 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.
  2. Bar scale. Each bar's width is sizeKb / maxSizeKb of the rail, with a 12% floor so even tiny resources read as a bar.
  3. Paint marker derivation. When firstPaintMs is omitted, the marker lands at 80ms + 6ms per blocking KB, clamped into the [80, totalMs - 40] window. Pass firstPaintMs to override.
  4. 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 under prefers-reduced-motion.

Props

PropTypeDefaultDescription
resourcesRenderBlockingViewResource[]requiredOrdered list of resources on the timeline.
titleReactNodeOptional heading above the panel.
descriptionReactNodeOptional sub-headline under the title.
firstPaintMsnumberderivedMilliseconds at which the paint marker lands.
totalMsnumber2000Total timeline span in milliseconds.
unitstring'KB'Display unit suffix for size values.
hidePaintMarkerbooleanfalseHide the paint marker overlay.
hideSizesbooleanfalseHide the per-row size caption.
headingAs'h2' | 'h3' | 'h4''h3'Tag for the title element.
paintLabelReactNodederivedOverride the paint marker caption.
classNamestringMerged onto the root via cn().

RenderBlockingViewResource

FieldTypeDescription
idstringStable identifier.
labelReactNodeVisible row label.
kind'css' | 'script' | 'font'Resource bucket; drives the leading badge.
blockingbooleantrue when the resource blocks first paint.
sizeKbnumberApproximate size; drives the bar width and caption.

Accessibility

  • The wrapper is a <section> with data-cb-edu="render-blocking-view" and data-cb-has-blocking="true | false" mirroring the blocking count.
  • The timeline rail carries an aria-label describing it as the render-blocking timeline; the paint marker itself is aria-hidden because the marker text duplicates information that is also conveyed in the summary strip.
  • Every resource row carries an aria-label of 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 the css-perf-simulator engine — a fixed three-mode radiogroup ('blocking | deferred | inline') that read a RenderBlockingTimeline (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, with firstPaintMs as an optional override.