FCP Budget Bar
A timeline bar that takes a set of ordered timing segments, stacks them left-to-right, and shows the running total against a budget line. The total readout takes a verdict tone — good / at / needs-improvement / poor — driven by where the total lands relative to the budget. Use it for Core Web Vitals (FCP / LCP / INP), request waterfalls (TTFB / download / render), or any "x ms of budget" reading.
Preview
Largest contentful paint timeline
Adjust the sliders to push the total past the budget tick.
1.97sbudget 2.50s
- FCP620ms
- LCP delta1.18s
- INP168ms
Customize
Segments
620ms
1180ms
168ms
Budget
2500ms
4000ms
Options
Installation
npx shadcn@latest add https://craftbits.dev/r/fcp-budget-bar.jsonUsage
import { FcpBudgetBar } from "@craft-bits/core";
<FcpBudgetBar
title="Largest contentful paint timeline"
budgetMs={2500}
segments={[
{ id: "fcp", label: "FCP", ms: 620 },
{ id: "lcp", label: "LCP delta", ms: 1180 },
{ id: "inp", label: "INP", ms: 168 },
]}
/>Override the timeline right-edge and worst-case threshold when the budget should sit further from the end:
<FcpBudgetBar
segments={[{ id: "fcp", label: "FCP", ms: 850 }]}
budgetMs={650}
timelineEndMs={2000}
poorThresholdMs={1200}
/>Anatomy
- Header. Optional
titleanddescription. Omit both for a chromeless panel. - Readout. Big total on the left,
budgetmirror on the right, optional verdict tag on the far right. - Bar. Segments fill in supplied order using stepped accent tints. The vertical divider marks the budget line.
- Legend. Per-segment label + duration row underneath, in tabular numerals.
- Axis. Numeric ticks for
0, the budget line, the poor threshold (when in range), and the timeline end.
Understanding the component
- Fill geometry. Each segment claims a slice of the bar proportional to its
ms / timelineEndMsand is placed at the cumulative offset of its predecessors. The timeline end auto-fits to the larger ofbudgetMs * 3ortotalMs * 1.25when not supplied so over-budget timelines stay readable. - Verdict.
total <= budgetMsreads asgood.totalat or beyondpoorThresholdMs(defaultbudgetMs * 1.85, mirroring the CWV split) reads aspoor. Anything between isneeds-improvement.tolerancedeclares a dead zone around the budget that flips the readout toat. - Colour. Segments use stepped tints of
cb-accentby default; passsegment.colorto override per row. - Motion. Each segment animates its width when totals change (spring
snap). Animations short-circuit underprefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
segments | FcpBudgetBarSegment[] | required | Ordered timing segments. |
budgetMs | number | required | Budget threshold in ms. Must be greater than zero. |
title | ReactNode | — | Optional heading above the bar. |
description | ReactNode | — | Optional sub-headline under the title. |
timelineEndMs | number | auto | Right-edge of the timeline. Auto-fits when omitted. |
tolerance | number | 0 | Dead zone around the budget — within it the row reads as at. |
poorThresholdMs | number | budgetMs * 1.85 | Threshold for the poor verdict. |
hideVerdict | boolean | false | Hide the verdict tag in the header. |
hideAxis | boolean | false | Hide the axis ticks under the bar. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
formatMs | (value) => string | ms/s | Formatter for readout and axis labels. |
className | string | — | Merged onto the root via cn(). |
FcpBudgetBarSegment
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. |
label | ReactNode | Visible segment label. |
ms | number | Duration this segment contributes. |
color | string | Optional override fill colour. |
Accessibility
- The wrapper is a
<section>withdata-cb-edu="fcp-budget-bar"and adata-cb-verdicthook so consumers can extend tone-specific styling without monkey-patching CSS. - The bar is a
role="progressbar"witharia-valuenow/aria-valuemin/aria-valuemaxset to the percentage of the timeline consumed. The total readout carries anaria-labeldescribing the verdict so it's conveyed without depending on colour. - The readout region uses
aria-live="polite"— value updates are announced without interrupting the user. - The legend is a
role="list"ofrole="listitem"rows so screen readers announce each segment as a sequence. - Animations are limited to segment width and short-circuit under
prefers-reduced-motion.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/perf-css/ui/FCPBudgetBar.tsx). The original was hard-wired to a single FCP value, a fixed 650ms budget, a fixed 2000ms timeline, and awinsflag that surfaced a project-specific "WINS" tag tied to the perf-css lab. This rewrite generalises it to any ordered{ id, label, ms }segments + a configurablebudgetMs, drops the lab-specific affordances, and adds the verdict tone, axis labels, and segment legend so it covers any "x ms of budget" timeline reading.