Skip to content
ilinxa/pro-ui

Stat Card

alphav0.1.2

Single-metric dashboard card — value, label, delta, and a dependency-free SVG sparkline with polarity-aware coloring.

Category: Data DisplayUpdated: 2026-08-11Created: 2026-05-09Author: ilinxa

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

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Register the @ilinxa namespace (once per project)Add to your components.json. Merge with existing config.
"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}
Install the component
pnpm dlx shadcn@latest add @ilinxa/stat-card

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/stat-card-fixtures

Preview

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

demo.tsxtsx

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-only increaseLabel /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

Tags

stat-cardmetrickpidashboardsparklinedelta

Dependencies

shadcn primitives: skeleton