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 %
| Prop | Type | Default | Description |
|---|---|---|---|
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.
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | The metric's name, set in mono caps. Required. |
value | React.ReactNode | — | The figure. A node, not a string, so a Countdown or a formatted number component can go here. Required. |
change | string | — | Change 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. |
icon | React.ComponentType<{ className?: string }> | — | Icon component for the top corner. Passed as a component, not an element, so the card sizes it. |
labels | StatCardLabels | — | The screen-reader phrasing for the change indicator: increase, decrease, change. |