One number that matters, with its change against a past period and room for a sparkline.
$ pnpm dlx shadcn@latest add https://dashral.com/r/metric-card.jsonInstallation
$ pnpm dlx shadcn@latest add https://dashral.com/r/metric-card.jsonAdds the component, the components it builds on, its npm packages and the theme tokens it uses. Using Dashral often? Add the registry to components.json once:
{
"registries": {
"@dashral": "https://dashral.com/r/{name}.json"
}
}Then add components by name:
$ pnpm dlx shadcn@latest add @dashral/metric-cardUsage
import { MetricCard } from "@/components/dashral/metric-card"
<MetricCard
label="Revenue"
value={48250}
formatOptions={{ style: "currency", currency: "USD" }}
change={12.4}
changeLabel="vs last month"
/>Examples
Live value
A new value counts up from the old one.
KPIs that drive a chart
The cards are tabs for the chart below (click or arrow keys); the period counts every number to its new value.
API reference
MetricCard
Also takes every prop of <div>(opens in a new tab).
| Prop | Type | Default |
|---|---|---|
label | ReactNodeWhat's measured. Required. | — |
value | number | nullRequired. null reads “—”. | — |
formatOptions / locale | Intl.NumberFormatOptions / stringe.g. { style: "currency", currency: "USD" }. | LocaleProvider's |
formatValue | (value: number) => stringWrites the value yourself, for what Intl can't (e.g. “2m 05s”). From a client component. | — |
change | number | nullChange against the comparison period. | — |
changeUnit | string" pts" for a change in a rate. | "%" |
countOnMount | booleanCount up from 0 when the card first shows. | false |
changeLabel | ReactNodee.g. "vs last month". | — |
invert | booleanDown is good (costs, churn): a fall shows in green. | false |
icon | ReactNodeShown in the corner. | — |
children | ReactNodeUnder the numbers, e.g. a Sparkline. | — |
Accessibility
- The change says “Up” or “Down” in words for screen readers, not just with an arrow and a color.
- While the number counts, screen readers get the final value once.
Motion
- A new value counts up (or down) from the old one, over about 1.5 × --duration-considered. Not with reduced motion.
- With countOnMount it also counts from 0 when it first shows; the server's final number is there without JavaScript.