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

Usage

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 title and description. Omit both for a chromeless panel.
  • Readout. Big total on the left, budget mirror 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

  1. Fill geometry. Each segment claims a slice of the bar proportional to its ms / timelineEndMs and is placed at the cumulative offset of its predecessors. The timeline end auto-fits to the larger of budgetMs * 3 or totalMs * 1.25 when not supplied so over-budget timelines stay readable.
  2. Verdict. total <= budgetMs reads as good. total at or beyond poorThresholdMs (default budgetMs * 1.85, mirroring the CWV split) reads as poor. Anything between is needs-improvement. tolerance declares a dead zone around the budget that flips the readout to at.
  3. Colour. Segments use stepped tints of cb-accent by default; pass segment.color to override per row.
  4. Motion. Each segment animates its width when totals change (spring snap). Animations short-circuit under prefers-reduced-motion.

Props

PropTypeDefaultDescription
segmentsFcpBudgetBarSegment[]requiredOrdered timing segments.
budgetMsnumberrequiredBudget threshold in ms. Must be greater than zero.
titleReactNodeOptional heading above the bar.
descriptionReactNodeOptional sub-headline under the title.
timelineEndMsnumberautoRight-edge of the timeline. Auto-fits when omitted.
tolerancenumber0Dead zone around the budget — within it the row reads as at.
poorThresholdMsnumberbudgetMs * 1.85Threshold for the poor verdict.
hideVerdictbooleanfalseHide the verdict tag in the header.
hideAxisbooleanfalseHide the axis ticks under the bar.
headingAs'h2' | 'h3' | 'h4''h3'Tag for the title element.
formatMs(value) => stringms/sFormatter for readout and axis labels.
classNamestringMerged onto the root via cn().

FcpBudgetBarSegment

FieldTypeDescription
idstringStable identifier.
labelReactNodeVisible segment label.
msnumberDuration this segment contributes.
colorstringOptional override fill colour.

Accessibility

  • The wrapper is a <section> with data-cb-edu="fcp-budget-bar" and a data-cb-verdict hook so consumers can extend tone-specific styling without monkey-patching CSS.
  • The bar is a role="progressbar" with aria-valuenow / aria-valuemin / aria-valuemax set to the percentage of the timeline consumed. The total readout carries an aria-label describing 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" of role="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 a wins flag 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 configurable budgetMs, 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.