Budget Widget
A compact panel that puts a set of current values against the budget each one is supposed to fit inside. Each row renders a segmented progress bar, the used / budget numbers, and a verdict tone — under / at / over. The bar keeps growing past the budget line up to a configurable cap so over-spend is visible at a glance, not silently clamped.
Preview
Performance budget
Adjust the sliders to push items over the line.
- LCP1.82s/2.50sms
- INP168/200ms
- CLS0.10/0.10
- JS bundle312/250KB
1 item over budget.
Customize
Used values
1820ms
168ms
312KB
Budgets
2500ms
250KB
Options
150%
Installation
npx shadcn@latest add https://craftbits.dev/r/budget-widget.jsonUsage
import { BudgetWidget } from "@craft-bits/core";
<BudgetWidget
title="Performance budget"
items={[
{ id: "lcp", label: "LCP", used: 1820, budget: 2500, unit: "ms" },
{ id: "inp", label: "INP", used: 168, budget: 200, unit: "ms" },
{ id: "js", label: "JS bundle", used: 312, budget: 250, unit: "KB" },
]}
/>Allow a small dead zone around the budget so noisy signals don't flip into the over band on a rounding wobble:
<BudgetWidget
items={[
{ id: "cls", label: "CLS", used: 0.1, budget: 0.1, unit: "", tolerance: 0.005 },
]}
/>Anatomy
- Header. Optional
titleanddescription. Omit both for a chromeless panel. - Row. Label on the left, the
used / budgetreading on the right, a thin progress bar underneath. The vertical divider on the bar marks the budget line — fills past it bleed into the over zone. - Verdict tone.
underpaints--cb-success,overpaints--cb-error,atpaints--cb-accent. Bar and numbers share the same tone. - Verdict summary. A one-line footer counts how many rows are over budget — hidden via
hideVerdict.
Understanding the component
- Fill geometry. Each bar fills
used / budget. The width is capped atmaxFillPercent(default150) so an item that's 5x over budget doesn't run off the layout. The budget threshold tick sits at100 / maxFillPercentof the bar's width. - Tolerance.
item.tolerancedeclares a dead zone around the budget — a row whose absolute distance from the budget is at or belowtolerancereads asatinstead ofunder/over. - Formatter. Each item can supply its own
format(value)function. The default switchesmstospast 1000 andKBtoMBpast 1000. - Motion. The bar fill animates when
usedorbudgetchanges (springsnap). The animation short-circuits underprefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | BudgetWidgetItem[] | required | Ordered budget rows. |
title | ReactNode | — | Optional heading above the bars. |
description | ReactNode | — | Optional sub-headline under the title. |
maxFillPercent | number | 150 | Bar growth cap, as a percentage of the budget. |
hideVerdict | boolean | false | Hide the verdict summary line. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
className | string | — | Merged onto the root via cn(). |
BudgetWidgetItem
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. |
label | ReactNode | Visible row label. |
used | number | Current spend / measurement. |
budget | number | Cap to compare against (must be greater than zero). |
unit | string | Display unit suffix (e.g. ms, KB). |
tolerance | number | Dead zone around the budget — within it the row reads as at. |
format | (value) => string | Custom formatter. |
Accessibility
- The wrapper is a
<section>withdata-cb-edu="budget-widget". Rows form arole="list"ofrole="listitem"entries so screen readers announce them as a sequence. - Each bar is a
role="progressbar"witharia-valuenow/aria-valuemin/aria-valuemaxset to the percentage of budget consumed (clamped at 100). The numeric reading carries anaria-labeldescribing the verdict so it's conveyed without depending on colour. - The list region uses
aria-live="polite"— value updates are announced without interrupting the user. - Each row exposes
data-cb-verdict="under" | "at" | "over"so consumers can extend tone-specific styling without monkey-patching CSS. - Animations are limited to the bar width and short-circuit under
prefers-reduced-motion.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/sdp-web-performance/ui/BudgetWidget.tsx). The original was hard-wired to aPerfContextand a fixed LCP/INP/CLS/JS gauge set with a "Simulate careless PR" affordance that mutated a global optimisation registry. This rewrite drops the context dependency, the regression simulator, and the fixed metric list — consumers pass their own{ id, label, used, budget }rows so the widget covers any budget-comparison surface (perf budgets, cloud spend, capacity plans, rate limits).