One metric with its change and a mini trend: KPI rows, dashboard tiles, record summaries. Values are formatted with Intl in the current locale; the Delta colour follows the metric's intent (is lower better?) and never carries meaning alone. Server-component compatible, no chart library.
- Visitors
- 28,412
- Up 12.4% vs previous 30 days
- Trending up over the last 9 periods
- vs previous 30 days
- Published posts
- 144
- Up 6 vs previous 30 days
- Trending up over the last 9 periods
- vs previous 30 days
- Form submissions
- 312
- Up 31.0% vs previous 30 days
- Trending up over the last 9 periods
- vs previous 30 days
- Avg. read time
- 2m 47s
- Down 4.1% vs previous 30 days
- Trending down over the last 9 periods
- vs previous 30 days
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { Stat, StatGroup } from "@vamos-ui/react";
<StatGroup>
<Stat label="Visitors" value={28412} delta={0.124} trend={[4, 6, 5, 8, 7, 9, 12]} comparison="vs previous 30 days" />
<Stat label="Avg. response" value={4320} format="duration" delta={-0.18} intent="lower-is-better" />
</StatGroup>Period selection (7D / 30D / 90D) belongs to the parent card, not to Stat.
No count-up animation: numbers are readable immediately and never re-announce.
Examples
Money (CRM)
format="compact-currency" → $1.2M; decimal strings are formatted exactly; pass { type, currency, options } for anything else.
- Won this month
- $1.2M
- Up 18.2%
- Trending up over the last 7 periods
- Pipeline value
- $3,815,400.00
- Down $82,000.00
- Average deal
- €18,400
- Up 5.4%
Lower is better
intent decides the colour: a drop in cancellations or response time is good news. neutral keeps it grey.
- Cancellation rate
- 2.4%
- Down 12.0% vs October
- Trending down over the last 6 periods
- vs October
- First response time
- 1h 12m
- Up 9.0% vs October
- Trending up over the last 5 periods
- vs October
- Tickets opened
- 86
- Up 14.0% vs October
- Trending up over the last 5 periods
- vs October
Progress to goal
target adds a goal bar; it turns success-coloured when reached.
- Revenue this month
- $12,840
- Up 8.0%
- Goal $46,80027%
- New sign-ups
- 520
- Up 21.0% Goal for November
- Goal reached104%
- Goal for November
Metrics in one surface (HR / PRM)
StatGroup variant="plain" with plain Stats: hairline dividers, one surface (one blur in glass). href turns a stat into a drill-down link.
Delta
Standalone change indicator for table cells and lists — arrow + sign + colour, spoken as "Up 12.4%".
Loading, no data, error
loading sizes the skeleton to label + value; null shows — and reads "No data"; error takes a retry slot.
- Visitors
- No data
- Conversion rate
- No data
- Tracking starts tomorrow
- Revenue
- Couldn’t reach the billing service.
en-US and tr-TR
Intl places the sign and percent: +12.4% vs +%12,4; compact $1.2M vs ₺1,2 Mn; durations 2m 47s vs 2 dk. 47 sn.
- Revenue · Gelir
- $1.2M
- Up 12.4%
- Avg. visit · Ort. ziyaret
- 2m 47s
- Down 4.1%
- Orders · Sipariş
- 28,412
- Up 3.1%
- Revenue · Gelir
- ₺1,2 Mn
- %12,4 arttı
- Avg. visit · Ort. ziyaret
- 2 dk. 47 sn.
- %4,1 azaldı
- Orders · Sipariş
- 28.412
- %3,1 arttı
API
Stat
| Prop | Type | Default |
|---|---|---|
labelMetric name (dt). | ReactNode | — |
valueNumbers are formatted; strings shown as is; null → "—" + No data. | number | string | null | — |
format"number" | "integer" | "decimal" | "currency" | "compact" | "compact-currency" | "percent" (ratio) | "duration" (s) | "bytes" | { type, currency, options }. | ValueFormat | "number" |
deltaChange; with deltaKind="percent" a ratio (0.124 → +12.4%). | number | null | — |
deltaKindHow the delta is formatted. | "percent" | "number" | "currency" | "percent" |
deltaVariantDelta look. | "text" | "badge" | "text" |
intentWhich direction is good news (colour of delta and trend). | "higher-is-better" | "lower-is-better" | "neutral" | "higher-is-better" |
comparisonFooter line; a string is appended to the spoken delta. | ReactNode | — |
trendSparkline values, oldest first. Summarised for screen readers. | (number | null)[] | — |
trendVariantSparkline style. | "line" | "area" | "bars" | "line" |
targetProgress-to-goal bar. | { value: number; label?: ReactNode } | — |
iconSmall icon before the label. | LucideIcon | — |
hrefWhole stat becomes a link. | string | — |
loadingSkeleton + aria-busy. | boolean | false |
error / errorActionError text in place of the value, plus a retry slot. | ReactNode | — |
variantOwn surface, a cell in a plain StatGroup, or no chrome. | "card" | "plain" | "inline" | "card" |
sizeValue 20 / 28 / 40 px (mono, tabular). | "sm" | "md" | "lg" | "md" |
labelsup, down, flat, noData, trendUp/Down/Flat, goal, goalReached. | Partial<StatLabels> | — |
Delta
| Prop | Type | Default |
|---|---|---|
valueThe change (percent kind = ratio). | number | null | — |
kindFormatting. | "percent" | "number" | "currency" | "percent" |
currencyISO 4217 for kind="currency" (default: provider currency). | string | — |
intentColour semantics. | StatIntent | "higher-is-better" |
variantPlain or soft pill. | "text" | "badge" | "text" |
precisionMax fraction digits. | number | — |
srLabel / comparisonOverride or extend the spoken sentence. | string | — |
StatGroup
| Prop | Type | Default |
|---|---|---|
columnsMax columns; container queries drop to 2 and 1. | 2 | 3 | 4 | 4 |
variantSeparate cards, or one surface with dividers. | "cards" | "plain" | "cards" |
StatLabel / StatValue / StatDelta / StatTrend / StatFooter / StatTarget
| Prop | Type | Default |
|---|---|---|
StatValue value / formatFormatted value; or pass children. | number | string | null / ValueFormat | — |
StatTarget value / target / labelGoal bar. | number / number / ReactNode | — |
...propsComposition parts (dt / dd) for custom layouts. | HTMLAttributes | — |