ui · Primitive
StatCard
@devalok/shilp-sutra/ui/stat-cardView in Storybook Preview
Monthly Revenue
$48,200
+12%vs last month
Active Users
8420
−3%vs last week
Variants
variant
Default
1,204
+4%
Elevated
1,204
+4%
Outline
1,204
+4%
accentStyle
None
98%
Icon chip
98%
Tint wash
98%
progress + secondary
Storage Used
4.2 GB
of 10 GB plan
loading
Reference
- Import: @devalok/shilp-sutra/ui/stat-card
- Server-safe: No
- Category: ui
Props
label: string (heading text)
title: string (alias for label)
value: string | number (REQUIRED)
prefix: string (before value, e.g. "$")
suffix: string (after value, e.g. "users")
delta: { value: string, direction: "up" | "down" | "neutral" }
deltaPlacement: "block" | "inline" (block = below value [default]; inline = on the value's baseline)
icon: ReactNode | ComponentType<{ className?: string }>
loading: boolean (renders skeleton)
comparisonLabel: string (shown after delta, e.g. "vs last month")
secondaryLabel: string (below main value, e.g. "of $50,000 target")
progress: number (0-100, renders thin progress bar below value)
variant: "default" | "elevated" | "outline" | "flat" (default = tonal border-card hairline, no shadow; elevated = shadow, no border; outline = strong border, no shadow; flat = filled, no edge) — delegated to Card
size: "sm" | "md" (default) | "lg" — delegated to Card's size axis; sm tightens padding to 16px and steps the value down to text-ds-2xl (use for dense KPI rows / narrow stat grids)
accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
flashSpeed: "fast" | "normal" | "slow" (default normal)
sparkline: number[] (renders mini SVG line chart)
onClick: () => void (makes card clickable with hover state)
href: string (makes card a link via LinkContext)
footer: ReactNode (below card body, e.g. "View details →")
Defaults
size="md"; all other props optional except value
Example
<StatCard
label="Revenue"
value="$48,200"
prefix="$"
delta={{ value: "+12%", direction: "up" }}
comparisonLabel="vs last month"
icon={<IconCurrencyDollar />}
accentStyle="icon"
flash="up"
/>
<StatCard
label="Storage"
value="4.2 GB"
secondaryLabel="of 10 GB"
progress={42}
sparkline={[10, 25, 18, 30, 42]}
footer={<a href="/storage">Manage storage →</a>}
/>Composability
- High-density metric card — optimized for dashboards. Everything optional except
value. Mix and match features (delta, sparkline, progress, secondary label, footer) per metric's needs. - Composes: card (specializes) — StatCard builds ON Card's surface (border/padding/shadow model) rather than re-rolling it; the pattern to copy for any new metric/widget card.
- Composes: stat-flash (contains) — the
flashprop mounts the standalone StatFlash primitive inside the card. - Context: LinkContext — resolves framework-specific Link components (Next.js, react-router); set
hrefin a LinkProvider-wrapped tree for client-side navigation without custom asChild wiring. - Interactive modes:
onClickmakes the entire card a button;hrefmakes it a link. Mutually exclusive — href wins if both are set. - Accent (composable, opt-in):
accentStyle="icon"wrapsiconin an accent chip (iconFill="soft" | "solid");accentStyle="tint"applies a subtle accent surface wash + accent value.variantpicks edge-vs-elevation. No colored rail — the DS never stacks a border + drop shadow (make-kit rule #6). Trend health reads fromdelta.direction(up=green, down=red). - Flash motion (opt-in):
flashmounts a toned state glyph (up/down/goal/record/alert/live, or{ tone, icon }) that settles toicon;flashSpeedtunes timing. Reuses the standaloneStatFlashprimitive. Honorsprefers-reduced-motion. - Sparkline: Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
- Icon auto-sizing: Accepts
ComponentType<{ className }>ORReactNode. The component prop (e.g.icon={IconBolt}) is preferred — icon is rendered at a consistent size. - Loading state:
loading={true}renders the full card skeleton — use during initial data fetch.
Gotchas
- delta.direction "up" = green, "down" = red, "neutral" = grey
labelandtitleare aliases — use either, not bothonClickandhrefare mutually exclusive — href takes precedencesparklineneeds at least 2 data points to render
Changes
v0.45.0
- Added
sizeprop (sm | md | lg, delegated to Card) —smtightens padding and steps the value totext-ds-2xlfor dense KPI grids. - Changed Internal rhythm is now flex gap (no margin stacking);
footerrenders behind a full-width rule (divider + CardFooter as direct Card children) instead of an insetborder-t. - Added
aria-busy="true"on the loading skeleton card.
v0.44.0
- BREAKING Renamed
surface→variant, widened to a 4-way scale (default|elevated|outline|flat). StatCard now composes<Card>, so surface, gap-model padding, and elevation all live in one place. Migration:surface="raised"→variant="default",surface="flat"→variant="outline". - Added
deltaPlacement("block"[default] |"inline") — inline rides the value's baseline for compact dashboards.
v0.43.0
- BREAKING Removed
accent(colored left-rail). UseaccentStyle("icon"|"tint") or rely ondeltafor trend colour. - Added
surface,accentStyle,iconFill,flash,flashSpeed. New standaloneStatFlashprimitive. Base no longer stacks border + shadow.
v0.2.0
- Added
iconprop now acceptsReact.ComponentType(e.g.,icon={IconBolt}) in addition toReactNode
v0.1.0
- Added Initial release