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
errorshouldn't block or blank the others. Don't lift a shared error state above the row and hide every tile on one failure; let eachKpiCardreport its own. value/changeare 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.emptyandunavailableare two different "no value" states.emptymeans the read succeeded but there is nothing for the current selection — a dash plus a printed reason (e.g. "No data for selection").unavailablemeans 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
StatTileinstead when the tile is one of many in a denser summary row (e.g. an engine-run summary) —KpiCardis sized for a handful of headline numbers, not a dozen. - Don't pass
directionwithout achangevalue if there's nothing to compare against — an arrow with no delta text reads as a rendering bug, not a real trend.