Stat

Shows one key number and how it moved.

Examples

Tickets today

Tickets today
549
Up+10.2%vs 498 yesterday

A workspace, today

Tickets today
1,284
Up+7.9%vs 1,190 yesterday
Resolved by agents
78.2%
Up+0.4%vs 77.9
First reply
1.4s
Down−6.7%vs 1.5

A prompt release over budget

Traffic
10%
Reply time p95
1.9s

Near the 2s budget

Spend
$1,284
Up+6.6%vs $1,204

With a trend

Tickets today
549
Up+10.2%vs 498 yesterday

Sizes

  • Runs
    549

    defaultdefault

  • Within SLA
    99.98%

    lg

  • Pro
    $20

    xl

Styles

  • Agents
    12

    plaindefault

  • Releases
    38

    framed

States

  • Runs
    549
    Up+10.2%vs 498

    up

  • Runs
    412
    Down−17.3%vs 498

    down

  • Runs
    498
    No change0%vs 498

    flat

  • Agents
    3
    Upnewvs 0

    new

  • Runs
    549
    Up+10.2%vs 498

    loading

  • Refunds
    None

    No usage yet

    empty

Usage

When to use it
Use
A view exists to answer how many or how much: spend, tickets, reply time.
Use
A row of two to four totals summarises the page below.
Use
A price or headline number on a marketing page: size xl.
Not for
The number is one cell among many of the same kind. Use Table.
Not for
The number is progress toward a limit. Use Progress.
Not for
The number is a count beside a label in a row. Use Badge.
Not for
The shape over time matters more than the latest value. Use Chart.

Rules

10 rules
Must
Pass from and to to StatDelta; never write the percent or arrow by hand. Because a typed "-8%" beside trend="up" puts the arrow against the figure and drops the "vs 507" baseline.
Must
Put a unit in StatUnit, not in the value string. Because StatValue breaks the string only after separators, so "412ms" sets the unit in the stat face at full size.
Never
Color a delta good or bad; say it in StatDescription. Because faster latency and more errors both go down, so a green or red delta tells half the readers the wrong thing.
Must
Set loading on every Stat in a row from one flag so they land together. Because tiles that resolve one by one shift the row and let the eye compare a new number with a stale one.
Must
Give a StatGroup role="group" and an aria-label naming the period. Because a screen reader reads four labels and values with no period, so "549" has no time frame.
Must
Put two or more stats in a StatGroup, never side by side in separate cards. Because a row of boxed numbers is the template dashboard, and each box adds two edges where one shared line does the job.
Should
Put two to four stats in a row and drop to two columns under 576px.
Should
Label with what is counted and the period in one to four words: Tickets today.
Should
Shorten a value past six characters with formatCounts (12.5M) or step down a size.
Never
Add a gradient, glow or count-up animation; use size lg or xl. Because a count-up shows numbers that were never true, and a reader who glances mid-count reads a wrong value.

Accessibility

5 checks
Nothing in a Stat is a tab stop.
The delta carries a sr-only word: Up, Down or No change.
A new value (from 0) reads as the word new, not an infinite percent.
The trend is role="img" named with the label and the trend in words; its sparkline and table are aria-hidden.
The root sets aria-busy while loading.

Edge cases

9 edge cases
10-digit value at 160px
Wraps between digit groups only; a long label wraps under itself.
No value
Write None in StatValue and say why in a StatDescription; from 0 to 0 draws no delta.
Right to left
Parts align to the start and the arrow mirrors.
Zoom 200%
The numeral doubles and wraps only between digit groups.
Loading
A 4ch block holds the value; delta and trend keep their space invisible.
Value ticks four times a second
Tabular figures keep the width; changes are not announced.
NaN in from or to
The delta renders nothing. On a failed request write None and say why.
A group under 448px
The cells stack, split by row rules instead of column rules.
Forced colors
The arrow stays currentColor; the frame edge draws in CanvasText, so the description carries trouble in words.

API

10 props
Stat size
'default' | 'lg' | 'xl' default 'default'
Stat variant
'plain' | 'framed' default 'plain'
Stat loading
boolean default false
Stat kind, status
Kind, 'warn' | 'fail'
StatGroup kind, variant
Kind, 'framed' | 'plain' default 'framed'
StatDelta from, to
number
StatDelta format
(n: number) => string
StatDelta period
string
StatDelta trend
'up' | 'down' | 'flat'
StatTrend data, labels, label
number[]

Tokens

20 tokens
--type-stat
400 32px/36px var(--font-sans)
--type-stat-lg
400 40px/44px var(--font-sans)
--type-stat-xl
400 48px/52px var(--font-sans)
--features-data
"tnum", "zero", "ss04"
--type-label
400 14px/16px var(--font-sans)
--text-2
oklch(var(--ink-c) / 0.80)
--type-body-lg
340 16px/22px var(--font-sans)
--type-heading-sm
400 20px/24px var(--font-sans)
--type-label-sm
400 12px/16px var(--font-sans)
--text-3
oklch(var(--ink-c) / 0.66)
--line-frame
oklch(var(--ink-c) / var(--line-frame-alpha))
--line-row
oklch(var(--ink-c) / 0.08)
--surface-page
var(--paper)
--radius-frame
10px
--surface-skeleton
color-mix(in oklch, oklch(var(--grey-c)), oklch(var(--ink-c)) var(--skeleton-step))
--accent-wash
var(--surface-selected)
--accent-mark
var(--ink)
--accent-line
var(--ink)
--warn-text
oklch(49% 0.100 68)
--fail-text
oklch(51% 0.180 26)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.