Metrics Panel
A compact dashboard primitive — a strip of metric tiles in an auto-fit grid. Each tile shows a label, a tabular-num value, an optional unit, an optional rating chip, and an optional target line. Designed for live updates: value swaps re-animate without a layout jump.
Preview
LCP1.8s
Good
INP220ms
Needs work
CLS0.14
Needs work
Customize
Options
Installation
npx shadcn@latest add https://craftbits.dev/r/metrics-panel.jsonUsage
import { MetricsPanel } from "@craft-bits/core";
<MetricsPanel
metrics={[
{ id: "lcp", label: "LCP", value: 1.8, unit: "s", trend: "good" },
{ id: "inp", label: "INP", value: 220, unit: "ms", trend: "warn" },
{ id: "cls", label: "CLS", value: 0.14, trend: "warn" },
]}
/>With targets and a custom value formatter:
<MetricsPanel
title="Field metrics"
metrics={[
{
id: "lcp",
label: "LCP",
value: 1800,
unit: "ms",
trend: "good",
format: (v) => (v >= 1000 ? (v / 1000).toFixed(1) + "s" : v + "ms"),
},
]}
target={{ lcp: 2500 }}
/>Anatomy
- Grid — CSS
gridwithauto-fit minmax(minTileWidth, 1fr). Tiles claim at leastminTileWidthand reflow on narrow viewports — no media queries. - Tile — small rounded card with a label, a large tabular-nums value, and an optional rating chip plus target line.
- Trend tone —
good/warn/badmap tocb-success/cb-warning/cb-erroron both the value and the chip.neutral(default) hides the chip. - Value transition — each value is keyed by itself, so the value
motion.spanre-mounts on change and re-runsinitial -> animate. Live dashboards swap smoothly without imperative motion-value plumbing.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
metrics | MetricsPanelMetric[] | required | Ordered metric tiles. |
target | Record<string, number> | — | Optional target value per metric id. |
formatTarget | (value, unit) => string | — | Formatter for target values. |
title | ReactNode | — | Optional headline above the grid. |
aria-label | string | 'Metrics' | Accessible label when no title is set. |
minTileWidth | string | '8rem' | Minimum tile width for the auto-fit grid. |
className | string | — | Merged onto the root via cn(). |
MetricsPanelMetric
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier, used as React key and to look up target. |
label | ReactNode | Visible label on top of the tile. |
value | number | Numeric value to display. |
unit | string | Optional unit suffix. |
trend | 'good' | 'warn' | 'bad' | 'neutral' | Optional semantic rating. |
format | (value, unit) => string | Override the value rendering. |
Accessibility
- The panel root is a
<section>with eitheraria-labeloraria-labelledby(whentitleis set). - The tile grid is a
role="list"withrole="listitem"children, so assistive tech treats it as a list of metrics. aria-live="polite"plusaria-atomic="false"scopes update announcements to the changing tile.- Reduced-motion users get static tiles — no enter animation, no value transition.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/sdp-web-performance/ui/MetricsPanel.tsx). The original was hard-coded to Core Web Vitals; this primitive keeps the dashboard shell and lets the consumer name, format, and rate every metric.