Long Task Widget
A compact main-thread timeline that paints each scheduled task as a horizontal block, colour-coded by whether it crosses the Long Tasks API threshold (50ms by default). Drop it into an INP lesson, a scheduler.yield() walkthrough, or any "why is my page janky" article.
Preview
Before yielding
Two long tasks hold the main thread for the full window.
hydrate — 280ms
parse — 120ms
400ms window2 long tasks
Customize
Timeline
400ms
50ms
50ms
Options
Installation
npx shadcn@latest add https://craftbits.dev/r/long-task-widget.jsonUsage
import { LongTaskWidget } from "@craft-bits/core";
<LongTaskWidget
title="Before yielding"
windowMs={400}
tasks={[
{ id: "hydrate", start: 0, duration: 280, label: "hydrate — 280ms" },
{ id: "parse", start: 280, duration: 120, label: "parse — 120ms" },
]}
/>Chunk the same work to land under the 50ms threshold:
<LongTaskWidget
title="After yielding"
windowMs={400}
tasks={Array.from({ length: 8 }, (_, i) => ({
id: "chunk-" + i,
start: i * 50,
duration: 50,
}))}
/>Anatomy
- Header. Optional
title(renders with thecb-labelstyle) and adescriptionsub-line. Omit both for a chromeless panel. - Track. A single horizontal track sized by
windowMs. Each task is positioned absolutely by itsstartand sized by itsduration. - Block tone. Tasks longer than
longTaskThresholdMspaint with--cb-error; the rest paint with--cb-success. Each block carriesdata-cb-verdict="long" | "short"so consumers can extend the palette. - Legend. A trailing chip summarises the long-task count. Hide it with
hideLegend.
Understanding the component
- Layout. Each task renders as an
absoluteblock.leftis derived fromstart / windowMs,widthfromduration / windowMs. The widget inferswindowMsfrom the latest task end when the prop is omitted. - Threshold.
longTaskThresholdMsdefaults to50— the Long Tasks API definition. Override it for stricter budgets (e.g.34for the 30 FPS frame budget) or for educational "what if we shrank the threshold" demos. - Tone. Tasks above the threshold tint
--cb-error; everything else tints--cb-success. The chip in the footer mirrors the worst verdict in the timeline. - Motion. Each block fades and
scaleX-springs in once on mount (springsnap). The animation short-circuits underprefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
tasks | LongTaskWidgetTask[] | required | Ordered list of task blocks. |
title | ReactNode | — | Optional heading above the track. |
description | ReactNode | — | Optional sub-headline under the title. |
windowMs | number | inferred | Total width of the timeline in ms. |
longTaskThresholdMs | number | 50 | Threshold above which a task reads as long. |
hideLegend | boolean | false | Hide the legend chip in the footer. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
className | string | — | Merged onto the root via cn(). |
LongTaskWidgetTask
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. |
start | number | Offset in ms from the start of the timeline. |
duration | number | Length of the task in ms. |
label | ReactNode | Optional in-block label. Defaults to the duration in ms. |
Accessibility
- The wrapper is a
<section>withdata-cb-edu="long-task-widget". The track is arole="img"with anaria-labelsummarising the task count, window width, and long-task verdict. - Each block carries a descriptive
aria-labelso screen-reader users get the same picture as sighted users. - Each block exposes
data-cb-verdict="long" | "short"for consumers extending the palette without monkey-patching CSS. - Animations are limited to the block enter and short-circuit under
prefers-reduced-motion.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/sdp-web-performance/ui/LongTaskWidget.tsx). The original was wired to a project-levelPerfContext, hard-coded a before/after row, and embedded a click simulator. This rewrite drops every project-specific prop and the simulator chrome — consumers pass their owntasksarray, so the widget visualises any timeline of main-thread work, not just the SDP web-performance lesson.