Stat
One to four headline numbers with meaning, each with a label, a unit and a source. The block is pure type and draws no chart; a trend belongs in a separate chart with its data table.
import { Stat, StatBlock } from '@fairgarden-private/design/components/Stat'
Each Stat has a caps label above a display-serif figure in lining tabular numerals, with the unit after a no-break space at about half the figure's size. Below it come an optional qualifier, a delta and a source note reference. Write figures with locale separators and state any rounding ("15,000+", "≈ 62"). A change is shown as ▲ or ▼ plus words, never by color alone.
Stat rows
Stat rows
A StatBlock row sets each stat in a rule-topped cell. With four or more stats, every figure uses the compact size at every width, so figures never resize as the row wraps. The row reflows on its own width: 1-up below 360 px; four stats 2 × 2 until 4-up from 1024 px; three stats 3-up from 1024 px (from 768 px when every figure has four characters or fewer); two stats 2-up, or 1-up below 768 px when a figure runs past five characters. It never auto-fits, which would orphan a stat.
- Members
- 15,000+
- 12% since 2025
- Species counted
- 312
- in the 2026 bird count
- Trail closures
- 3
- 2 fewer than last spring
- Volunteer hours
- 8,640 h
- in 2026
- Wetland restored
- ≈ 62 ha
- since 2019
1. Conservation easements and owned preserves, as of June 2026.
'use client'
import { Link } from '@fairgarden-private/design/components/Link'
import { Stat, StatBlock } from '@fairgarden-private/design/components/Stat'
import styles from './row.module.css'
/** Four stats set every figure compact; two stats keep the full stat size. */
export function StatRow() {
return (
<div className={styles.stack}>
<StatBlock>
<Stat
label="Land protected"
value="4,200"
unit="ha"
qualifier="across 38 preserves"
source={
<Link kind="noteref" href="#stat-note-1">
1
</Link>
}
/>
<Stat label="Members" value="15,000+" delta="12% since 2025" deltaDirection="up" />
<Stat label="Species counted" value="312" qualifier="in the 2026 bird count" />
<Stat label="Trail closures" value="3" delta="2 fewer than last spring" deltaDirection="down" />
</StatBlock>
<StatBlock>
<Stat label="Volunteer hours" value="8,640" unit="h" qualifier="in 2026" />
<Stat label="Wetland restored" value="≈ 62" unit="ha" qualifier="since 2019" />
</StatBlock>
<p id="stat-note-1" className={styles.note}>
1. Conservation easements and owned preserves, as of June 2026.
</p>
</div>
)
}
Hero and inline
Hero and inline
kind="hero" centers one numeral on the blob mount, in the secondary's step 9 over a step 7 under-disc, with the caption below. Use it once per page, and only on light grounds and night, never on forest or saturated grounds. Keep the numeral short (up to about three characters) so it fits the disc. kind="inline" sets one numeral in running text with its qualifier; pass a TooltipTrigger kind="term" as the qualifier for a defined term.
42
The new index answers a query in 38 ms at the 95th percentile, fast enough for the search field to update as you type.
'use client'
import { Stat, StatBlock } from '@fairgarden-private/design/components/Stat'
import styles from './hero.module.css'
/** The hero stat on the blob mount (once per page), and a stat inline in running text. */
export function StatHero() {
return (
<div className={styles.stack}>
<StatBlock kind="hero">
<Stat label="Nesting pairs" value="42" qualifier="of bald eagles on the river, up from 6 in 2001" />
</StatBlock>
<p className={styles.text}>
The new index answers a query in{' '}
<StatBlock kind="inline">
<Stat value="38" unit="ms" qualifier="at the 95th percentile" />
</StatBlock>
, fast enough for the search field to update as you type.
</p>
</div>
)
}
In print
Figures, labels, units, deltas and sources print black. Rows print 4-up, or 2 × 2 when a figure is long, and cells never split. The hero's mount is decoration and is hidden in print, so only its numeral remains.
API Reference
StatBlock
The stat block. Reflows on its own width (the stat-row container): 1-up
below 360 px; four stats 2 × 2 until 4-up from 1024 px; three stats
3-up from 1024 (from 768 when every figure has ≤ 4 characters); two
stats 2-up (1-up below 768 when a figure runs past 5 characters).
| Prop | Type | Description |
|---|---|---|
compact | | Sets every figure in |
kind | |
|
primary | | Primary Radix scale: every text and the cell rules. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: only the hero’s mount discs. Never defaulted. |
id | | |
children | |
|
className | |
Stat
One stat inside a StatBlock: label, figure with unit, qualifier, delta
and source. Give every stat a label, a unit where one applies, and a
source.
| Prop | Type | Description |
|---|---|---|
label | | The caps label above the figure ( |
value | | The figure in lining tabular numerals, with locale separators and a stated rounding (“67,000,000″, “15,000+", “≈ 2.4 M”). A currency symbol leads it at full size. |
delta | | The change as words: “12% since 2023″. Drawn after a ▲ or ▼, never color alone. |
deltaDirection | | The direction of |
deltaLabel | | The glyph’s accessible name. Default “Up” or “Down”. |
qualifier | | A qualifier (“per 1M docs”) in |
source | | The source: a note reference (§8.12), in |
unit | | The unit, set after a no-break space at roughly half the figure’s size. |
className | |
Additional types
StatBlockProps
Props for StatBlock: the kind, compact and color axes, and the stats.
type StatBlockProps = {
/**
* `row` (default): 1–4 rule-topped cells in a `dl`. `hero`: one numeral
* on the blob mount, centered, with a caption, once per page and only on
* light grounds and night (never forest or saturated grounds). `inline`:
* one numeral in running text with its qualifier.
*/
kind?: 'row' | 'hero' | 'inline' | null;
/**
* Sets every figure in `type-stat-compact`. Default: on for four or more
* stats, at every width, so figures never change size as the row wraps.
*/
compact?: boolean;
/**
* Primary Radix scale: every text and the cell rules. Never defaulted;
* omitted, it inherits the scope [D133].
*/
primary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/** Secondary Radix scale: only the hero's mount discs. Never defaulted. */
secondary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
className?: string;
id?: string;
/** `Stat` elements: 1–4 in a row, exactly one in a hero or inline block. */
children?: React.ReactNode;
}StatProps
Props for Stat: the label, figure, unit, qualifier, delta and source of one stat.
type StatProps = {
/** The caps label above the figure (`type-label`); author it in sentence case. */
label?: React.ReactNode;
/**
* The figure in lining tabular numerals, with locale separators and a
* stated rounding ("67,000,000", "15,000+", "≈ 2.4 M"). A currency symbol
* leads it at full size.
*/
value: React.ReactNode;
/** The unit, set after a no-break space at roughly half the figure's size. */
unit?: React.ReactNode;
/** A qualifier ("per 1M docs") in `type-caption`; pass a `TooltipTrigger kind="term"` for a defined term. */
qualifier?: React.ReactNode;
/** The change as words: "12% since 2023". Drawn after a ▲ or ▼, never color alone. */
delta?: React.ReactNode;
/** The direction of `delta`: `up` ▲ or `down` ▼. */
deltaDirection?: 'up' | 'down';
/** The glyph's accessible name. Default "Up" or "Down". */
deltaLabel?: string;
/** The source: a note reference (§8.12), in `type-small`. */
source?: React.ReactNode;
className?: string;
}Specification: DESIGN-SYSTEM.md §8.8 (stat typography), §12.10 (stat block) and §5.10.2 (column counts).