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

Usage

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 grid with auto-fit minmax(minTileWidth, 1fr). Tiles claim at least minTileWidth and 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 tonegood / warn / bad map to cb-success / cb-warning / cb-error on both the value and the chip. neutral (default) hides the chip.
  • Value transition — each value is keyed by itself, so the value motion.span re-mounts on change and re-runs initial -> animate. Live dashboards swap smoothly without imperative motion-value plumbing.

Props

PropTypeDefaultDescription
metricsMetricsPanelMetric[]requiredOrdered metric tiles.
targetRecord<string, number>Optional target value per metric id.
formatTarget(value, unit) => stringFormatter for target values.
titleReactNodeOptional headline above the grid.
aria-labelstring'Metrics'Accessible label when no title is set.
minTileWidthstring'8rem'Minimum tile width for the auto-fit grid.
classNamestringMerged onto the root via cn().

MetricsPanelMetric

FieldTypeDescription
idstringStable identifier, used as React key and to look up target.
labelReactNodeVisible label on top of the tile.
valuenumberNumeric value to display.
unitstringOptional unit suffix.
trend'good' | 'warn' | 'bad' | 'neutral'Optional semantic rating.
format(value, unit) => stringOverride the value rendering.

Accessibility

  • The panel root is a <section> with either aria-label or aria-labelledby (when title is set).
  • The tile grid is a role="list" with role="listitem" children, so assistive tech treats it as a list of metrics.
  • aria-live="polite" plus aria-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.