Charts & metrics
Stat Card
A KPI, its change and a sparkline in one tile
detent-stat-card
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-data
- Group
- Charts & metrics
8.7 KB gzip for the class module
Variants
A StatGrid of three tiles (revenue, churn with good-when="down", latency) and one tile with no trend
data. Tab into a tile and press ArrowRight / ArrowLeft to scrub the sparkline,
Escape to clear. Numbers are local demo data: nothing is fetched and no money is moved.
Ready.
Recorded states
reduced motionrecorded state; no setteractiverecorded state; no setter
Usage
Import
import '/components/stat-card/define.js';
// or register it yourself
import { DetentStatCard } from '/components/stat-card/stat-card.js';
Attributes
animatedcolordeltagood-whenlabelperiodpreviousvalue
Properties
formattrendtrendLabels
Slots
defaultstat-grid.children
Events
No events are recorded for this component.
Example
Runnable example: site/examples/stat-card.ts. The snippet below is generated from the catalogued attributes.
<detent-stat-card
animated
color
delta
good-when="up"
label
period="vs previous period"
previous
value
></detent-stat-card>
Property-only values
const element = document.querySelector('detent-stat-card');
if (element !== null) {
// trend: number[]
element.trend = [];
// trendLabels: string[]
element.trendLabels = [];
// format: (n: number) => string
element.format = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/StatCard.tsxsha256 281b6647295b…
Native files
The native module and its artifacts:
src/components/stat-card/stat-card.tssrc/components/stat-card/styles.csssrc/components/stat-card/define.tsdocs/components/stat-card.mdsite/examples/stat-card.tstests/browser/stat-card.spec.tsevidence/components/stat-card.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
animated | animated | boolean | true | Presence semantics: present means true, absent means false. |
color | color | string | CHART_COLORS[0] | Kebab-case attribute; camelCase property. |
delta | delta | number | — | Kebab-case attribute; camelCase property. |
good-when | goodWhen | "up" | "down" | "up" | Kebab-case attribute; camelCase property. |
label | label | string | — | Kebab-case attribute; camelCase property. |
period | period | string | "vs previous period" | Kebab-case attribute; camelCase property. |
previous | previous | number | — | Kebab-case attribute; camelCase property. |
value | value | number | — | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
format | (n: number) => string | Intl.NumberFormat | Property-only: assign from script; not accepted as an attribute. |
trend | number[] | — | Property-only: assign from script; not accepted as an attribute. |
trendLabels | string[] | — | Property-only: assign from script; not accepted as an attribute. |
Events
No custom events are recorded for this component.
Slots
| Slot | Notes |
|---|---|
default | Consumer content; rendered through a slot, never re-created. |
stat-grid.children | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
- StatGrid ships as the auto-fitting [part="grid"] of the same tag (PC-2.1 keeps one tag per row): extra tiles are assigned to the stat-grid.children slot instead of being React children of a second component.
- The row's flattened stat-grid.class-name pseudo-attribute is not implemented: a React className prop cannot be an HTML attribute and a dotted name is not kebab-case (PC-2.3). Grid styling goes through [part="grid"] and --detent-stat-card-radius.
- Upstream's tweens keep their timing: the count-up is a rAF tween on EASE_OUT cubic-bezier(0.23, 1, 0.32, 1) (1.1 s intro, 0.22 s after); on the first render with a line, the line draws through pathLength (1.1 s, 0.15 s delay, EASE_OUT) and the area fades in (0.8 s, 0.5 s delay, ease-in-out) through WAAPI, once, as upstream's initial; the latest-point pulse is a CSS keyframe (scale 1 to 2.8, opacity 0.4 to 0, 1.8 s ease-out). Reduced motion removes the pulse, the draw and the fade and makes the count-up immediate.
- The scrub cursor moves between points on upstream's spring (600/44/0.5) through the shared helper; its first point of a scrub appears in place (upstream jump()), and reduced motion moves it at once.
- The tile is themed from --detent-color-* tokens (upstream hard-codes neutral shades), so light and dark are both supported (PC-9.5).
- Long labels are ellipsised with a title-free truncation; upstream uses Tailwind's truncate with no accessible alternative, so the full label remains in the card's aria-label here.
What it does
A KPI tile with a label, a count-up value, a delta pill, a period line and a scrubbable sparkline, plus an auto-fitting grid for several tiles.
Upstream behaviour
- Count-up headline number on mount that follows the sparkline while scrubbing
- Delta pill computes change from a previous value or accepts an explicit percentage
- Direction-aware coloring with goodWhen="down" for churn, latency, and costs
- Hand-written SVG sparkline with a smooth monotone curve and gradient fill
- Pointer and touch scrubbing with a spring cursor and per-point labels
- Keyboard support with ArrowLeft, ArrowRight, and Escape
- StatGrid auto-fits tiles from a single mobile column to full-width rows
- Light and dark themes plus prefers-reduced-motion support
Differences from upstream
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
- StatGrid ships as the auto-fitting [part="grid"] of the same tag (PC-2.1 keeps one tag per row): extra tiles are assigned to the stat-grid.children slot instead of being React children of a second component.
- The row's flattened stat-grid.class-name pseudo-attribute is not implemented: a React className prop cannot be an HTML attribute and a dotted name is not kebab-case (PC-2.3). Grid styling goes through [part="grid"] and --detent-stat-card-radius.
- Upstream's tweens keep their timing: the count-up is a rAF tween on EASE_OUT cubic-bezier(0.23, 1, 0.32, 1) (1.1 s intro, 0.22 s after); on the first render with a line, the line draws through pathLength (1.1 s, 0.15 s delay, EASE_OUT) and the area fades in (0.8 s, 0.5 s delay, ease-in-out) through WAAPI, once, as upstream's initial; the latest-point pulse is a CSS keyframe (scale 1 to 2.8, opacity 0.4 to 0, 1.8 s ease-out). Reduced motion removes the pulse, the draw and the fade and makes the count-up immediate.
- The scrub cursor moves between points on upstream's spring (600/44/0.5) through the shared helper; its first point of a scrub appears in place (upstream jump()), and reduced motion moves it at once.
- The tile is themed from --detent-color-* tokens (upstream hard-codes neutral shades), so light and dark are both supported (PC-9.5).
- Long labels are ellipsised with a title-free truncation; upstream uses Tailwind's truncate with no accessible alternative, so the full label remains in the card's aria-label here.