FairGarden Design

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.

Land protected
4,200 ha
across 38 preserves
1
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.

StatRow.tsx
'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

Nesting pairsof bald eagles on the river, up from 6 in 2001

The new index answers a query in 38 ms at the 95th percentile, fast enough for the search field to update as you type.

StatHero.tsx
'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).

PropTypeDescription
compact
boolean | undefined

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.

kind
'row' | 'hero' | 'inline' | null | undefined

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.

primary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: every text and the cell rules. Never defaulted; omitted, it inherits the scope [D133].

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
| undefined

Secondary Radix scale: only the hero’s mount discs. Never defaulted.

id
string | undefined
children
React.ReactNode | undefined

Stat elements: 1–4 in a row, exactly one in a hero or inline block.

className
string | undefined

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.

PropTypeDescription
label
React.ReactNode | undefined

The caps label above the figure (type-label); author it in sentence case.

value
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.

delta
React.ReactNode | undefined

The change as words: “12% since 2023″. Drawn after a ▲ or ▼, never color alone.

deltaDirection
'up' | 'down' | undefined

The direction of delta: up ▲ or down ▼.

deltaLabel
string | undefined

The glyph’s accessible name. Default “Up” or “Down”.

qualifier
React.ReactNode | undefined

A qualifier (“per 1M docs”) in type-caption; pass a TooltipTrigger kind="term" for a defined term.

source
React.ReactNode | undefined

The source: a note reference (§8.12), in type-small.

unit
React.ReactNode | undefined

The unit, set after a no-break space at roughly half the figure’s size.

className
string | undefined

Additional types

statBlock
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).