Skip to content

Stat Card

Analytics tiles: a figure, how it moved, and one picture of its data, on a card or joined in a panel.

import { Stat, StatBars, StatBreakdown, StatBreakdownPart, StatCard, StatDelta, StatGroup, StatMeter, StatNote, StatValue } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use stat tiles for the handful of numbers a dashboard opens with: revenue, orders, latency, accounts. Each one is built like an instrument readout: a small label, one big figure with its unit set small and muted, how it moved (a triangle and the change in the good or bad ink, then what it's compared against), and at most one picture of its data underneath. For a trend people need to read values from, use a Chart.

import { Stat, StatBars, StatDelta, StatGroup, StatMeter, StatValue } from '@youj/react'

<StatGroup aria-label="This week" className="sm:grid-cols-2">
  <Stat label="Monthly recurring revenue">
    <StatValue value={121140} format={{ style: 'currency', currency: 'USD', notation: 'compact', maximumFractionDigits: 1 }} />
    <StatDelta value={0.064}>vs. last month</StatDelta>
    <StatMeter value={121140} max={130000} marker={120000}>of the $130K target</StatMeter>
  </Stat>
  <Stat label="Orders" action={<span>7 days</span>}>
    <StatValue value={1502} />
    <StatDelta value={0.08}>vs. last week</StatDelta>
    <StatBars data={[182, 204, 197, 221, 236, 214, 248]} previous={[171, 188, 201, 194, 210, 205, 219]} />
  </Stat>
</StatGroup>
  • Stat is one tile: a group named by its label, with an action at the end of the label's line (a period, a live marker, a small menu) and loading.
  • StatGroup joins tiles into one panel, split by hairlines at the theme's stroke. Any number of tiles wraps cleanly; set the columns with grid-cols-*.
  • StatValue is the figure. format takes Intl.NumberFormat options, so currencies, percentages, compact numbers and units format in the YoujProvider's locale (en-US without one), or the locale you pass; unit adds words Intl has no unit for ("seats").
  • StatDelta is the change: a fraction by default (0.124 is 12.4%), or in the value's own terms with format. Its children are the comparison. StatNote is a muted line for when there's no change to show.

Press Next reading in the preview: each figure rolls to its new value, each change counts to its own, the bars morph and a triangle turns when its change does. Refetch shows loading: the label stays, and the figures wait behind a skeleton.

Reference

Propslabel, action, loading… (51)

Props

Stat

Stat props
PropType
labelRequiredReactNode

What is measured: "Monthly recurring revenue". Names the tile.

–
actionReactNode

At the end of the label's line: a period ("30 days"), a live marker, or a small menu button.

–
loadingboolean

The figures and picture wait behind a skeleton; the tile is busy.

default false

false
loadingLabelstring

Announced while loading. Default "Loading {label}" when the label is a string.

–
childrenReactNode–
Other props pass through: extends ComponentProps<'div'>.

StatGroup

StatGroup takes Card's props. Its columns auto-fit at 11rem and up; set your own with grid-cols-*.

StatValue

StatValue props
PropType
valueRequirednumber–
fromnumber

Where the first roll starts. Default 0; pass the value itself to start settled and roll only on a change.

–
formatIntl.NumberFormatOptions

Intl.NumberFormat options: currency, percent, compact notation, units.

–
localestring

Default: the YoujProvider locale.

–
unitReactNode

Words Intl has no unit for, set small and muted after the figure: "seats", "/mo".

–
size'sm' | 'default' | 'lg'

sm for dense strips, lg for the one figure a panel leads with.

default 'default'

'default'
Other props pass through: extends ComponentProps<'span'>.

StatDelta

StatDelta props
PropType
valueRequirednumber

The change: a fraction (0.124 = 12.4%) by default, or in the value's own terms with format.

–
formatIntl.NumberFormatOptions

Intl.NumberFormat options for the change.

default PERCENT

PERCENT
invertboolean

Down is good for this metric (latency, churn, cost): a fall takes the good ink.

default false

false
localestring

Default: the YoujProvider locale.

–
childrenReactNode

What it's compared against, muted after the change: "vs. last month".

–
Other props pass through: extends ComponentProps<'span'>.

StatBars

StatBars props
PropType
dataRequiredreadonly number[]

The readings, oldest first: one bar each.

–
previousreadonly number[]

Last period's readings, aligned with data: a tick on each bar, so you see where it beat them.

–
toneStatTone
'accent''neutral''success''warning''danger''chart-1''chart-2''chart-3''chart-4''chart-5'

The lit bar's color; the rest are a quiet tint of it.

default 'neutral'

'neutral'
highlightnumber | 'none'

Which bar is lit. Default: the latest. none lights none.

–
maxnumber

The top of the scale. Default: the largest reading (or tick).

–
labelstring

Accessible name, e.g. "Orders per hour today, peaking at 11 am". Without it the bars are decorative.

–
Other props pass through: extends ComponentProps<'div'>.

StatMeter

StatMeter props
PropType
valueRequirednumber–
maxRequirednumber

The whole, or the target.

–
markernumber

A notch on the track at this value: where the pace says you should be by now, or a limit inside a bigger whole. Behind it, the fill turns warning.

–
toneStatTone
'accent''neutral''success''warning''danger''chart-1''chart-2''chart-3''chart-4''chart-5'

The fill. Default accent; success once the value reaches max.

–
segmentsnumber

Draw this many cells instead of a solid bar.

–
localestring

Default: the YoujProvider locale.

–
childrenReactNode

The words after the share: "of the $150K target".

–
detailReactNode

At the end of the caption: "$42K to go".

–
Other props pass through: extends ComponentProps<'div'>.

StatBreakdown

StatBreakdown props
PropType
partsRequiredStatBreakdownPart[]–
formatIntl.NumberFormatOptions

Intl.NumberFormat options for each part's figure in the key.

–
localestring

Default: the YoujProvider locale.

–
legendboolean

The key under the bar: each part's swatch, name and figure.

default true

true
Other props pass through: extends ComponentProps<'div'>.

StatCard

StatCard props
PropType
labelRequiredReactNode

What is measured: "Monthly recurring revenue".

–
valueRequirednumber–
formatIntl.NumberFormatOptions

Intl.NumberFormat options for the value: currency, percent, compact, units.

–
localestring

Default: the YoujProvider locale.

–
unitReactNode

Words Intl has no unit for, small and muted after the value: "seats".

–
previousnumber

The previous period's value. Gives the change when delta is not set.

–
deltanumber

Change as a fraction (0.124 = +12.4%). Default: from previous.

–
invertboolean

Down is good for this metric (latency, churn, cost): a fall is shown as good news.

default false

false
comparisonReactNode

The period compared against, after the change: "vs. last month". Without a change it's a note.

–
actionReactNode

A slot beside the label: a period, a menu, a link.

–
chartReactNode

A slot under the figures: StatBars, StatMeter, StatBreakdown or a Sparkline.

–
loadingboolean

default false

false
loadingLabelstring

Announced while loading. Default "Loading {label}" when the label is a string.

–
spotlightboolean

Light the card with motion's Spotlight effect.

–
variant'default' | 'inset' | 'panel'

inset: in the Bezel style, a shell carrying the head, with the rest in a well inside it at the concentric radius. Classic draws it as the default card. panel: in every style, a slim header strip (the title as a small label, an info tip, a menu button) over the content on a raised surface inset in the shell.

–
liftboolean

Rise on hover and focus: for cards that are one big target.

–
size'default' | 'sm'–

StatCard passes the rest of its props to Card, at size="sm".

Right to leftLabels, figures and captions follow the page's direction; the…

Right to left

Labels, figures and captions follow the page's direction; the hairlines between tiles move to the inline end, and meters and breakdowns fill from the inline start. Bars and sparklines keep time running left to right.

MotionValue changes, change changes, change turns… · reduced motion

Motion

Motion tokens
MomentTokens
Value changesmotion’s Counter rolls each digit to the new number, a tight stagger step per place from the right
duration.deliberateease.emphasized

560ms · cubic-bezier(0.6, 0, 0.1, 1)

560mscubic-bezier(0.6, 0, 0.1, 1)
Change changesthe figure counts to its new value
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)
Change turnsthe triangle turns to face the new direction
spring.snappy

0.2s, bounce 0

0.2s, bounce 0
Bars arriveeach rises from the baseline in turn, left to right, when they scroll into view (a clip, so the rounded tops stay round)
duration.deliberateease.enter

560ms · cubic-bezier(0.16, 1, 0.3, 1)

560mscubic-bezier(0.16, 1, 0.3, 1)
New readingsbars and ticks morph from where they are
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)
Meter fillsShareBar’s edge travels to the share; a new value retargets from where it is
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Breakdown opensthe parts open from the inline start in turn; a new split glides
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)
Figures arriveContentSwap: rise out of a light blur while the height follows
duration.baseease.enter

220ms · cubic-bezier(0.16, 1, 0.3, 1)

220mscubic-bezier(0.16, 1, 0.3, 1)

Reduced motion

Figures, bars, meters and breakdowns are drawn at their values: nothing rolls, rises or opens.

Reduced motion

Values swap to the new number without rolling, the triangle shows its new direction at once, bars, meters and breakdowns are drawn at their values, and loaded figures replace the skeleton without a fade or blur.

AccessibilityARIA, announcements

Accessibility

  • Each tile is a group (role="group") named by its label, so "Monthly recurring revenue, $121.1K, up 6.4% vs. last month" is read as one thing.
  • StatDelta says "Up", "Down" or "No change" in words before the figure. The triangle and its color are decorative.
  • The pictures are decorative by default: the figures and words beside them say the same. Give StatBars a label to make it an image with a name ("Orders by day this week, rising"). StatBreakdown's key is a list of each part's name and figure.
  • While loading, a tile sets aria-busy, keeps its label, and announces "Loading" and the label (or loadingLabel); the new figures are read when they arrive.
  • An action needs its own name, such as aria-label="Revenue options".