Design System
Components

KPI Card

A headline KPI tile with its own loading/error variant — each tile can fail independently.

An Eyebrow label, a large value with directional change, and an optional caption — for the headline number in a KPI row. The type is a discriminated union: pass isLoading, error, empty, unavailable, or the value/direction/change trio, never a mix; TypeScript enforces which fields are valid together.

Rail Revenue
$34,018,000+18.5%

PY: $28,704,533

Guests
On-Time Departures
Failed to load
Utilization

No data for selection

ETP
—

Props

Prop

Type

Implementation notes

  • Each tile owns its own failed state on purpose. A KPI row typically fires several independent fetches — one tile's error shouldn't block or blank the others. Don't lift a shared error state above the row and hide every tile on one failure; let each KpiCard report its own.
  • value/change are pre-formatted strings, not raw numbers. Formatting (currency, thousands separators, sign) is the caller's job — this keeps the component locale/format-agnostic rather than guessing at a number format.
  • empty and unavailable are two different "no value" states. empty means the read succeeded but there is nothing for the current selection — a dash plus a printed reason (e.g. "No data for selection"). unavailable means the metric cannot be shown at all right now (e.g. a ratio whose denominator is zero) — a dash with the reason as the accessible name only, so the row doesn't repeat a caption down every tile that can't compute.

Do / Don't

  • Do reach for StatTile instead when the tile is one of many in a denser summary row (e.g. an engine-run summary) — KpiCard is sized for a handful of headline numbers, not a dozen.
  • Don't pass direction without a change value if there's nothing to compare against — an arrow with no delta text reads as a rendering bug, not a real trend.

On this page