Usage
import { StatCard } from "@voila.dev/ui/stat-card";<StatCard.Root>
<StatCard.Header>
<StatCard.Label>Projects published</StatCard.Label>
<StatCard.Delta trend="up">+12%</StatCard.Delta>
</StatCard.Header>
<StatCard.Value>1,284</StatCard.Value>
</StatCard.Root>The label and the delta share the header row; the value sits under them on its own line, so a row of cards lines its numbers up whether or not each one has a delta to show.
trend picks the arrow and the colour; it is separate from the text because a
falling number is not always bad — cancellations going down is a down trend
you would want to read as good.
Recipes
A sparkline for the shape of the number
StatCard.Chart is a full-bleed slot pinned to the bottom edge: it cancels the
card's bottom padding and the card clips the chart to its rounded corners.
Constrain the height on the Chart.Root itself and drop the margins, since a
sparkline has no axes to make room for.
<StatCard.Chart>
<Chart.Root
config={config}
data={data}
x={{ key: "month", type: "point" }}
y={{ keys: ["projects"] }}
className="aspect-auto h-16 w-full"
margin={{ top: 4, right: 0, bottom: 0, left: 0 }}
interactive
>
<Chart.Cursor />
<Chart.Area />
<Chart.Tooltip />
</Chart.Root>
</StatCard.Chart>The cursor and the tooltip work in a 64-pixel-tall chart exactly as they do in a full-size one, so a sparkline can still be read value by value.
The three sparkline shapes
An area for a volume that accumulates, a line for a rate, bars for counts per
period. Bars need a band axis to sit in; a line or an area wants point.
Falling numbers that are good news
<StatCard.Delta trend="down" className="text-success">
-45%
</StatCard.Delta>trend keeps the arrow pointing down while the colour says the metric is
moving the right way.
Metric status against an objective
<StatCard.Root status="alerting">
<StatCard.Header>
<StatCard.Label>Cancellations</StatCard.Label>
</StatCard.Header>
<StatCard.Value>17</StatCard.Value>
<StatCard.Target>Target: under 10</StatCard.Target>
</StatCard.Root>status sets data-status on the root and tints the frame with the semantic
tokens — alerting destructive, below-objective warning, on-track the
default frame. The tint goes on the frame, not the value, so the number stays
readable and a scan of the grid shows where the fires are.
StatCard.Target is a quiet line under the value for the objective the tint
refers to. The copy is yours — the component only sets the muted small type.
Loading and empty
Chart.Skeleton and Chart.Empty slot straight into StatCard.Chart. Pad the
slot back (px-4, plus pb-4 for the empty state) so the placeholder sits
inside the card instead of bleeding to its edges like a real chart.
Compact, for dense dashboards
size="sm" on the root tightens the padding and the gaps; the anatomy is
unchanged.
A KPI strip
API
StatCard.Root
| Prop | Type | Default | |
|---|---|---|---|
size | "default" | "sm" | — | |
status | "alerting" | "below-objective" | "on-track" | — | Health of the metric against its objective. The tint goes on the frame,
not the value, so the number stays readable and a scan of the grid shows
where the fires are: alerting renders a destructive frame,
below-objective a warning one, on-track keeps the default frame. |
render | ComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>> | — | Allows you to replace the component's HTML element
with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render. |
Plus the DOM props of the element it renders. Source: stat-card/components/stat-card-root.tsx.
StatCard.Chart
Plus every <div> prop. Source: stat-card/components/stat-card-chart.tsx.
StatCard.Delta
| Prop | Type | Default | |
|---|---|---|---|
trend | "down" | "neutral" | "up" | "neutral" | Direction of the change: up renders success, down destructive,
neutral muted. Set it by what the movement means, not by its sign —
churn going up is not a success. |
Plus every <div> prop. Source: stat-card/components/stat-card-delta.tsx.
StatCard.Header
| Prop | Type | Default | |
|---|---|---|---|
render | ComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>> | — | Allows you to replace the component's HTML element
with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render. |
Plus every <div> prop. Source: stat-card/components/stat-card-header.tsx.
StatCard.Label
| Prop | Type | Default | |
|---|---|---|---|
render | ComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>> | — | Allows you to replace the component's HTML element
with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render. |
Plus every <div> prop. Source: stat-card/components/stat-card-label.tsx.
StatCard.Target
| Prop | Type | Default | |
|---|---|---|---|
render | ComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>> | — | Allows you to replace the component's HTML element
with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render. |
Plus every <div> prop. Source: stat-card/components/stat-card-target.tsx.
StatCard.Value
| Prop | Type | Default | |
|---|---|---|---|
render | ComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>> | — | Allows you to replace the component's HTML element
with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render. |
Plus every <div> prop. Source: stat-card/components/stat-card-value.tsx.