CWV Gauges
A Core Web Vitals dashboard row — one radial gauge per metric, each pinned to a [good, poor] threshold pair. The component does the bucketing (good / warn / bad), the sweep math, and the tone. Generic enough for any threshold-rated metric, not just CWV.
Preview
1.8sLCP
220msINP
0.14CLS
Customize
Options
Installation
npx shadcn@latest add https://craftbits.dev/r/cwv-gauges.jsonUsage
import { CwvGauges } from "@craft-bits/core";
<CwvGauges
metrics={[
{ id: "lcp", label: "LCP", value: 1.8, unit: "s", thresholds: [2.5, 4.0] },
{ id: "inp", label: "INP", value: 220, unit: "ms", thresholds: [200, 500] },
{ id: "cls", label: "CLS", value: 0.14, thresholds: [0.1, 0.25] },
]}
/>Highlight the gauge whose value just changed:
<CwvGauges metrics={metrics} pulseKey="lcp" />Invert the rating direction for "higher is better" metrics:
<CwvGauges
metrics={[
{
id: "hit",
label: "Hit rate",
value: 92,
unit: "%",
thresholds: [90, 70],
higherIsBetter: true,
},
]}
/>Anatomy
- Bucketing — each metric carries
thresholds: [good, poor]. For lower-is-better metrics,value <= goodisgood,value <= pooriswarn, anything else isbad.higherIsBetterflips the comparison. - Sweep — the radial arc length is a normalised fraction of
value / poor, clamped to[0.04, 0.95]so the ring is always visible but never closes a full circle. - Tone — the bucket maps to
cb-success/cb-warning/cb-erroron the sweep stroke, the value text, and the rating chip. - Pulse — when
pulseKeymatches a metric'sid, that gauge scales briefly — a quiet way to spotlight a value that just changed.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
metrics | CwvGaugeMetric[] | required | Ordered gauges. |
pulseKey | string | null | null | Metric id to briefly highlight. |
aria-label | string | 'Core Web Vitals' | Accessible group label. |
minTileWidth | string | '11rem' | Minimum gauge tile width for the auto-fit grid. |
className | string | — | Merged onto the root via cn(). |
CwvGaugeMetric
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier — used as the React key and matched against pulseKey. |
label | ReactNode | Short visible label (e.g. "LCP"). |
value | number | Raw numeric value plotted on the gauge. |
unit | string | Optional unit suffix. |
thresholds | [number, number] | [goodCutoff, poorCutoff] — same units as value. |
description | ReactNode | Optional subtitle under the rating chip. |
format | (value, unit) => string | Override the value rendering. |
higherIsBetter | boolean | Invert the comparison for metrics where larger is better. |
Accessibility
- The row root is a
role="group"with anaria-label("Core Web Vitals" by default). - Each gauge is a
role="meter"witharia-valuenow/aria-valuemin/aria-valuemaxso screen readers announce the value and its scale. - The
aria-labelon each meter composes label, formatted value, and rating bucket. - Reduced-motion users get a static sweep — no animated dash offset, no pulse.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/perf-cwv/ui/CwvGauges.tsx). The original was bound to a CWV simulator engine with hard-codedformatLcp/rateLcp/ etc.; this primitive drops the engine coupling and lets the consumer name, format, and threshold any metric.