Stat Card
alphav0.1.2Single-metric dashboard card — value, label, delta, and a dependency-free SVG sparkline with polarity-aware coloring.
Context
Use anywhere a single calibrated metric needs to surface — admin / ops dashboards, product analytics surfaces, financial dashboards, observability views, marketing metrics. Object-shape `delta` from day one (F-cross-12-correct). Default delta format = locale-aware `Intl.NumberFormat` percent (`0.124` → `+12.4%`); `betterIsHigher: false` flips the green/red semantics for cost / error metrics where ↑ is bad. Sibling `<StatCardSparkline>` export covers standalone sparkline use without the card chrome. First component in the metrics-domain family; future siblings (TBD): kpi-grid (responsive layout), gauge-card (radial), comparison-card (two-up).
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/stat-cardAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/stat-card-fixturesPreview
- Revenue this month
- $12,431
- +12.4%increasevs last period
- Error rate (last 24h)
- 42.00%
- +8%increasevs last period
- Active users
- 1,234
- Average response time
- 42.7 ms
- -8%decreasevs last period
Default variant — 4-card strip. Revenue defaults to betterIsHigher: true (↑ green); error rate sets betterIsHigher: false (↑ red); latency went down with betterIsHigher: false (↓ green).
Demo source
Usage
Quick start
Single-metric dashboard widget. Required props are value and label. Add delta for vs-prior-period change, trend for a sparkline, icon for a leading visual marker, variant to change density.
import { StatCard } from "@/components/stat-card"
import { DollarSign } from "lucide-react"
export function RevenueCard() {
return (
<StatCard
value={12431}
label="Revenue this month"
icon={DollarSign}
formatValue={(v) => `$${v.toLocaleString()}`}
delta={{ value: 0.124 }}
trend={[8200, 8800, 9100, 10200, 10900, 11500, 12100, 12431]}
/>
)
}Variants
default— value + label + delta + sparkline. The canonical KPI strip card.compact— smaller value, no sparkline. Sidebar widgets and dense KPI grids.detailed— larger value + mandatory sparkline + larger icon. Hero KPI cards.
Delta polarity (`betterIsHigher`)
Default: betterIsHigher: true — a positive delta colors green, a negative delta colors red. Set false for cost / error metrics where ↑ is bad. The default delta.format is locale-aware Intl.NumberFormat percent — delta.value is a fraction (0.124 = +12.4%).
<StatCard
value={errorRate}
label="Error rate (last 24h)"
delta={{ value: 0.08, betterIsHigher: false }} // up = bad → red
/>
<StatCard
value={signupCount}
label="New signups this week"
delta={{
value: 1240,
format: (v) => v.toLocaleString(undefined, { signDisplay: "exceptZero" }),
}}
/>Custom value rendering
Use renderValue for unit superscripts, tooltips, or any composite value treatment. Receives { value, loading }.
<StatCard
value={42.7}
label="Average response time"
renderValue={({ value }) => (
<span>
{value.toFixed(1)}
<span className="text-muted-foreground text-base ml-1">ms</span>
</span>
)}
/>Sparkline
Pass trend as a flat number[]; the component renders a built-in pure-SVG sparkline (no charting peer dep). Cap at 100 points; larger arrays are uniformly downsampled. For non-line shapes (bars, dual-axis) use the renderTrend slot. The StatCardSparkline sub-component is exported separately for standalone use:
import { StatCardSparkline } from "@/components/stat-card"
<StatCardSparkline
data={[42, 48, 55, 62, 68, 71, 67, 64]}
className="h-8 w-32 text-primary"
/>Loading + empty
loading=true renders a shape-matched skeleton — aria-busy+ sr-only loading announcement. Skeleton size matches the loaded variant so there's no layout shift on hydration. value=undefined renders labels.emptyValueLabel (default —) at the value position; same height, muted color.
i18n
Labels are overridable via the labels prop. Defaults are English; override deltaPrefix, deltaPeriod, loadingLabel, increaseLabel, decreaseLabel, emptyValueLabel. Number formatting via delta.format with a locale arg.
Accessibility
- Root is
<dl>— screen readers announce label-value pairs natively. - Sparkline + arrow are
aria-hidden; the delta has an sr-onlyincreaseLabel/decreaseLabelso SRs say "12.4% increase" not "12.4% up arrow". - Linked variant uses overlay-link pattern: focus-visible ring on the card; the link element is transparent on top.
- Loading state is
aria-busy="true"with sr-only loading announcement.
Features
- 3 variants — default / compact / detailed
- Object-shape delta callback (F-cross-12-correct from day one)
- betterIsHigher boolean for cost / error semantics (default true)
- Built-in pure-SVG sparkline (no charting peer dep, ~50 LOC)
- Up to 100 trend points (uniform downsampling for larger datasets)
- <StatCardSparkline> sibling export for standalone use
- Default Intl.NumberFormat percent formatter (locale-aware, signDisplay: exceptZero)
- Loading state with shape-matched skeleton
- Empty state ('—' at value position when value is undefined)
- <dl> root for native screen-reader label-value pairs
- Polymorphic root via linkComponent + href (overlay-link pattern)
- renderValue + renderTrend escape hatches (object-shape contexts)
- WCAG 2.1 AA — sr-only delta semantic, aria-hidden sparkline + arrow, focus-visible ring on link