Stat strip
Metrics joined into one object, each a label, a tabular number, and a hint.
Stat strip draws a row of numbers that belong together, like a campaign's reach, as one
object instead of a card per number. Each stat is a label, a number, and an optional hint
that puts the number in context, and the strip reads the same on a page, a card, or a tray.
For a single number on its own card, use Card's
CardMetric.
- Recipients
- 1,204
- Delivered
- 1,148
- 95% of recipients
- Opened
- 493
- 43% of delivered
- Clicked
- 88
- 8% of delivered
- Sent
- 1,204
- Bounced
- 41
- 3% of sent
- Unsubscribed
- 6
- <1% of sent
Usage
import { Stat, StatStrip } from '@/components/ui/stat-strip';
<StatStrip>
<Stat label="Delivered" value={delivered.toLocaleString()} hint="95% of recipients" />
<Stat label="Opened" value={opened.toLocaleString()} hint="43% of delivered" />
</StatStrip>value takes any node, so format numbers the way the rest of the app does. Put each Stat
directly inside the strip, because the strip counts its children to decide how many columns
to draw.
One object, drawn by the gap
Each stat is a tile on the sunken well (bg-surface-sunken), and the strip sets the tiles apart by the stroke
width (--sys-stroke, 1px or 2px with the look's UI weight), so the surface underneath
draws the dividers. The strip clips its corners to rounded-lg.
Description list draws its rows the same
way. A tile is a 14px label in text-fg-muted, a number in text-heading-3 with tabular
figures, and a 12px hint in text-fg-muted, also tabular.
Layout
From sm up the strip takes one equal column per stat. Below sm it drops to two a row, and
an odd last stat spans the row so the strip stays square. Keep a strip to about five stats,
and past that split it into two strips under their own
Section titles. The breakpoint follows the window, not the
strip's own width, so a strip in a narrow column on a wide screen stays in one row. Narrow
the window to see the three-stat strip above put its last stat on a row of its own.
Props
StatStrip takes every <dl> attribute, and Stat takes every <div> attribute, plus these on
Stat.
| Prop | Type | Default | What it does |
|---|---|---|---|
label | ReactNode | What the number counts, drawn above the number. | |
value | ReactNode | The number, already formatted. | |
hint | ReactNode | A line of context under the number, like "41% of recipients". It is left out when empty. |
Accessibility
- The strip is a
<dl>and each stat a<div>holding a<dt>for the label and a<dd>for the value, so a screen reader reads each number with its label. A hint is a second<dd>in the same group. - Every figure is text, so a screen reader gets the values as written. Color does not carry any meaning, since the tiles share one tint.
- A strip is not focusable and announces nothing when a value changes, so wrap it in a live region yourself if numbers update while a person is looking at it.
- Nothing moves, so reduced motion has nothing to turn off.
- In forced colors the tint and the surface become the same system background, so each tile
draws a 1px
CanvasTextoutline into the gap and the strip a border, which keeps the dividers.
When to use something else
- Card with
CardMetricfor one number that stands on its own. - Description list for label and value pairs that read as a record, not as headline numbers.
- Sparkline when the shape of a number over time matters more than the number, placed beside a stat.
- Segmented bar when the numbers are parts of one whole and the split is the point.
- Progress for one value moving toward a total.
Install
shadcn add @gesso/stat-strip