Stat card
One figure with its change and a sparkline you can read point by point, by pointer, touch or keyboard. No chart library.
Installation
npx shadcn@latest add @quireui-pro/stat-cardPart of Pro. Add the Pro registry with your token once, as shown in Installation, and the CLI brings in the free parts it builds on as well.
Usage
<StatCard
label="Revenue"
value={50335}
format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
delta={15.9}
period="vs September"
/>A number goes through format, which takes the options of Intl.NumberFormat or your own function. A string, like a plan name, shows as it is.
Long numbers and definitions
notation: "compact" turns 1,204,000 into $1.2M, in the headline and in the sparkline alike. action holds anything at the end of the label row, like a tooltip saying how the figure is worked out, so nobody has to guess what is counted.
<StatCard
label="Annual run rate"
value={1204000}
format={{ style: "currency", currency: "USD", notation: "compact", maximumFractionDigits: 1 }}
action={
<Tooltip>
<TooltipTrigger aria-label="About annual run rate">
<InfoIcon />
</TooltipTrigger>
<TooltipContent>This month's recurring revenue × 12</TooltipContent>
</Tooltip>
}
/>The change
delta is the signed change against period. Up is green and down is red, and an arrow says the same for anyone who cannot tell the colours apart. For figures where less is better, like response time or churn, lowerIsBetter swaps the colours and keeps the arrow pointing the way the number went.
A percentage misleads on small counts: going from 3 to 4 reads as "33%". deltaUnit="value" shows the change itself.
Reading a point
series draws the sparkline. Move along it and the headline becomes that point, with its label where the period was. Nothing else on the card moves, since the delta has its own row. Let go and the total comes back.
<StatCard
label="Revenue"
value={50335}
format={money}
series={[
{ label: "Oct 1", value: 1320 },
{ label: "Oct 2", value: 1410 },
// ...
]}
previous={[1180, 1255 /* ... */]}
/>previous is the period before, point for point. It draws a dashed line behind the series, and gives each point its own change. Without it the delta stays out of the way while you read a point, because a single point has nothing to be compared with.
Bars
chart="bars" draws one bar per point, for counts like signups or orders per day. Bars stand on zero, so their heights can be compared; a line zooms in on its own range, which suits a level that moves, like response time. Reading a point works the same, and the bar being read goes solid.
A period still running
Today's figure always looks like a crash at the end of the line, because the day is not over. partial says the last point is still being counted: the line turns dashed for its last step, or the bar fades, and the point reads "Oct 30, so far". It has no change of its own, since half a day against a whole one says nothing.
<StatCard label="Revenue" value={48710} format={money} series={revenue} partial />A row of cards
StatCardGroup wraps to fit its width and ties the sparklines together: looking at Oct 12 on one card shows Oct 12 on all of them, so you can see what Signups did on the day Revenue dipped. Points are matched by label, and a card without that point stays on its total.
<StatCardGroup aria-label="October">
<StatCard label="Revenue" {...revenue} />
<StatCard label="Signups" {...signups} />
<StatCard label="Response time" lowerIsBetter {...latency} />
</StatCardGroup>Goals
goal adds a bar showing how far value is toward a target.
Links
link makes the whole card one target that leads to the full report. The sparkline stays a sparkline, so reading a point never navigates.
<StatCard label="Revenue" link={{ render: <Link href="/revenue" /> }} {...revenue} />Loading, empty and failed
loading shows placeholders in the shape of the figures, so nothing moves when they arrive. Pass series={[]} while loading to hold the sparkline's space. value={null} says there is no data, which is different from a zero. error says the figures could not load, and onRetry offers Retry.
Accessibility
The direction of a change is read out in words, like "Up 15.9%, vs September", and never relies on colour. The sparkline is a slider: screen readers hear its name, "Revenue over time", then each point as you move, like "Oct 12: $1,745, Up 15.6%".
| Key | Action |
|---|---|
| ← / →, ↓ / ↑ | Previous or next point |
| PageDown / PageUp | Back or forward a tenth of the series |
| Home / End | First or last point |
| Esc | Back to the total |
Motion
None on purpose. The line and the headline follow the pointer and the keys without easing, and figures do not count up: a number that is still rolling is a number you cannot read yet.
Stat card or something else?
- A plain amount in a table or a list: a Table with tabular numbers.
- How much of a limit is used, like storage or seats: a Meter.
API reference
StatCard
One figure with its change and, when there is a series, a sparkline that reads out any point. Hovering, touching or arrowing along the line swaps the headline for that point.
Prop
Type
Style it with [data-slot="stat-card"].
StatCardGroup
A row of stat cards that wraps to fit. Their sparklines move together: looking at Oct 12 on one shows Oct 12 on all of them, matched by each point's label.
Also takes every <div> attribute. Style it with [data-slot="stat-card-group"].