Stat
ProA KPI/metric card for dashboards: a label, a big tabular-nums value, a directional trend chip, and optional icon or sparkline. Compose the named parts into a tile, then drop a row of them above a Data Table.
Installation#
Usage#
Stat is composed from named parts, like Card. The Stat root owns the surface, variant, and density; the parts (StatLabel, StatValue, StatTrend) read it from context.
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
A tile reads top-to-bottom: a StatHeader pairs the label with a tinted StatIcon (or an action), the StatValue carries the figure in tabular-nums so it never reflows, and a StatFooter sets the trend beside a muted comparison caption.
import {
StatGroup,
Stat,
StatHeader,
StatLabel,
StatIcon,
StatValue,
StatFooter,
StatTrend,
StatCaption,
StatSparkline,
} from "@/components/ui/stat"
<StatGroup>
<Stat>
<StatHeader>
<StatLabel />
<StatIcon />
</StatHeader>
<StatValue />
<StatFooter>
<StatTrend />
<StatCaption />
</StatFooter>
<StatSparkline data={[...]} />
</Stat>
</StatGroup>Group#
StatGroup fuses a row of tiles into one segmented surface, the way a dashboard bands its headline KPIs together instead of floating them as separate cards. The group owns the single border, radius, and shadow; each nested Stat senses the group and sheds its own chrome to lie flush, with hairline dividers between. Set the widest-breakpoint column count with columns (2 | 3 | 4 | 5 | 6); it steps down gracefully on narrow screens.
Inside a pane that already draws its own borders (a header band, a split view), variant="plain" drops the frame: no ring, radius, or shadow, only the dividers between tiles. The pane’s rules frame the band, so no edge is drawn twice and no rounded corner meets a straight line.
Trend#
StatTrend is a soft chip whose arrow and color come from direction: up reads green, down red, and neutral muted. For metrics where down is the good outcome (refunds, churn, bounce rate), set inverted: the color flips to match the meaning while the arrow keeps pointing the honest way. The chip never wraps or shrinks, so a “+1.1 pts” in a narrow tile stays one chip and the row beside it gives way.
Sparkline#
StatSparkline is built on Chart in sparkline mode: a soft area fill under a trend line that bleeds to the bottom and side edges of the tile. It paints in currentColor, so set the hue with a text utility (text-success, text-destructive) and it themes across all four palettes for free. Pass tooltip to light up each point on hover with a gliding bubble.
Count up#
Pass countUp to StatValue and the figure counts up from zero to its value the first time it scrolls into view, the same trigger the marketing sections use, re-counting each time it scrolls back in. It climbs fast and decelerates onto the final figure, so it reads as a real count-up. It reads the string children ("4,100+", "$4.2M", "4.6×") and preserves the prefix, suffix, and comma/decimal format as it counts, riding the built-in tabular-nums so the row never jitters as the count climbs. It no-ops for number-less content and honors prefers-reduced-motion by showing the final figure immediately.
Variants#
default sits on a hairline border with a soft shadow; outline drops the shadow for flat, gridded dashboards; elevated trades the border for a lifted shadow when a tile needs to float; plain has no tile at all.
Without a frame#
Most dashboards lead a chart or a card with one big figure, and that figure is not a tile. variant="plain" drops the ring, the shadow, the fill and the padding, and keeps everything else: the display-face figure, the density’s type ramp, and a tighter rhythm between the parts, since there is no frame left to space them against. Lay it on the sheet beside a chart, or inside a Card, where the card is the frame. Inside a StatGroup the group already owns the frame, so a plain tile keeps the group’s padding.
Parts on their own#
The parts do not need a Stat around them. StatValue, StatTrend, StatLabel and StatCaption (and the header, footer and icon rows) render anywhere with the plain look at the ambient density, so the headline figure of a chart card is a StatValue, and the trend chip beside a number in a description is a StatTrend, the same chip a tile carries. countUp works there too. Only StatSparkline needs a Stat, because it bleeds to the tile’s edges.
Density#
Density is Koala’s cross-cutting spacing axis (see Density). For Stat it tunes padding, gap, the icon tile, and the value size. compact is the dashboard default; comfortable is roomier. Set it per-tile or for a whole grid with DensityProvider.
Interactive#
A KPI tile is data, not a control, so it stays inert by default. When the whole tile should drill into a detail view, pass asChild to render it as a link and interactive to add the pointer, hover lift, and focus ring. For a per-tile menu instead, drop a Button into the header.
API reference#
Stat
The tile root: owns the surface, variant and density, and shares them with its parts through context. Forwards all native <div> props; every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "outline" | "elevated" | "plain" | "default" | default is an edge ring plus a soft lift, outline the bare ring, elevated a card fill with a shadow, plain no frame or padding at all. |
| density | "compact" | "comfortable" | "compact" | Padding, gap, value size and icon tile. Inherits the nearest DensityProvider when omitted. |
| interactive | boolean | - | Adds the pointer, a hover lift and a focus ring, for a tile that is a link or button. |
| asChild | boolean | false | Renders onto the child element via Radix Slot (e.g. an <a>). |
StatGroup
Fuses a row of tiles into one segmented surface with hairline dividers; nested Stats shed their own chrome. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "outline" | "elevated" | "plain" | "default" | The group's frame, mirroring Stat's variants. elevated sets the card as the tiles' ground; plain drops the frame and keeps only the dividers, for a band inside a pane that already has its own borders. |
| columns | 2 | 3 | 4 | 5 | 6 | 4 | Columns at the widest breakpoint; steps down to one, then two, on narrower screens. |
| asChild | boolean | false | Renders onto the child element via Radix Slot. |
| CSS variable | Description |
|---|---|
| --surface | Set to var(--card) by elevated; grouped tiles paint it as their opaque fill. |
StatHeader
A row that sets the label on the left and an icon or action on the right. Forwards all native <div> props.
StatLabel
The metric's name, muted and medium weight. Renders inside a Stat or on its own. Forwards all native <div> props.
StatIcon
A tinted tile for a glyph, hidden from assistive tech. Forwards all native <div> props.
StatValue
The headline figure in the display face with tabular-nums, sized by density. Renders inside a Stat or on its own at the ambient density. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| countUp | boolean | false | Counts a string figure ("$4.2M", "74%") up from zero each time it scrolls into view, keeping its prefix, suffix and format. Shows the final figure under reduced motion. |
StatFooter
A row that pairs the trend with the caption. Forwards all native <div> props.
StatTrend
The trend chip: an arrow and a soft tone. Never wraps or shrinks. Forwards all native <span> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | "up" | "down" | "neutral" | "up" | The arrow, and by default the tone: up green, down red, neutral muted. |
| inverted | boolean | false | Flips the tone without flipping the arrow, for metrics where down is good (churn, refunds). |
| Data attribute | Description |
|---|---|
| data-direction | up, down or neutral. |
StatCaption
The muted line that frames the figure (a period, a comparison). Forwards all native <div> props.
StatSparkline
A chromeless trend built on Chart, bleeding to the tile's bottom and side edges. Paints in currentColor, so set the hue with a text utility. Must sit inside a Stat; forwards native <div> props except children.
| Prop | Type | Default | Description |
|---|---|---|---|
| data* | number[] | - | The series, drawn as a soft area under a line. |
| strokeWidth | number | 2 | Line stroke width in px. |
| tooltip | boolean | false | Shows a gliding tooltip with each point's value on hover. |
| valueFormatter | (value: number) => string | - | Formats each point's value in the tooltip. |