Skip to content
Data Display

Stat Card

One metric on a card: a name, a figure, and an optional change indicator. An organism — a composition of Card with a fixed internal layout, rather than a primitive you assemble yourself.

Total Revenue

$45,231.89

Increase of +20.1% from last month +20.1% from last month

Import

import { StatCard } from "@mikenotthepope/substrateui"

A row of them

The usual shape is a Grid of four across the top of a dashboard. The card has no width of its own, so the grid sets it.

Total Revenue

$45,231.89

Increase of +20.1% +20.1%

Subscriptions

2,350

Increase of +180.1% +180.1%

Sales

12,234

Increase of +19% +19%

Active Now

573

Decrease of -201 since last hour -201 since last hour

The sentiment is yours to set

changeType is not derived from the sign of change, and deliberately so. A falling error rate, a falling churn number, and a falling latency figure are all good news with a minus sign in front of them; guessing from the string would get them backwards.

The component also does no arithmetic. Whatever you pass as change is printed as given, including the sign and the unit.

Error Rate

0.4%

Increase of -1.2pp -1.2pp

Median Session

4m 12s

Change of ±0s ±0s

A node as the value

value takes a node, not a string, so anything that renders a figure can go in it — a live Countdown, a sparkline, a number that animates. The card supplies the type scale and the layout.

Free tier used

68/100

Change of +4 today +4 today

Labels

The change indicator's direction is drawn as a ▲, ▼, or • glyph, which is marked aria-hidden— a screen reader reading "black up-pointing triangle" before a number is noise. These three functions supply the words that replace it. Override one instance with the labels prop, or every instance through LabelsProvider's statCard key.

All three are functions rather than strings, so the change text can sit anywhere in the sentence a language needs it. That does mean a function cannot cross the server/client boundary: pass these from a Client Component, or set them once on LabelsProvider, which already lives on the client side of that line. This page is a Client Component for exactly that reason.

Chiffre d'affaires

45 231,89 €

En hausse de +20,1 % +20,1 %

PropTypeDefaultDescription
increase(change: string) => string"Increase of {change}"Announced for changeType="positive".
decrease(change: string) => string"Decrease of {change}"Announced for changeType="negative".
change(change: string) => string"Change of {change}"Announced for changeType="neutral".

Accessibility

Colour is not the only cue for the change direction: each sentiment carries a glyph as well as a colour, so up and down are distinguishable without seeing red and green. The glyph is hidden from assistive technology and replaced by the label text.

title renders as a p, not a heading, so a row of cards adds nothing to the document outline. Where the titles are the structure of the page, put a real heading above the grid — or wrap each card in a region you have named yourself.

The title is set in mono uppercase via tracking-wider rather than text-transform alone. Keep the string in sentence case: some screen readers read an all-caps string letter by letter.

A figure abbreviated for space — "2.4k", "$1.2M" — is read as written. Where the exact number matters, put it in the accessible name or offer the underlying table.

API Reference

Every prop not listed goes to the underlying Card, and from there to its div — including className, which is where the card's width comes from. The internal layout is fixed; for a metric that needs a different arrangement, compose Card directly.

PropTypeDefaultDescription
titlestringThe metric's name, set in mono caps. Required.
valueReact.ReactNodeThe figure. A node, not a string, so a Countdown or a formatted number component can go here. Required.
changestringChange text such as "+12%". Rendered as given — the component does no arithmetic and adds no sign.
changeType"positive" | "negative" | "neutral""neutral"Which colour and glyph the change gets. Your call, not derived from the string: a falling error rate is good news.
iconReact.ComponentType<{ className?: string }>Icon component for the top corner. Passed as a component, not an element, so the card sizes it.
labelsStatCardLabelsThe screen-reader phrasing for the change indicator: increase, decrease, change.