Families, scale, weights, and complete ds-text-* recipes. Use a semantic text utility when authoring page typography so family, size, line height, weight, and tracking move together; do not reassemble a size token by hand.
Family slots are available ingredients; family roles are what consumers use. Rebrand by changing which slot a role points to, not by making agents infer usage from a family name.
Use roles in components and application surfaces. A serif-led system can point body, heading, and display at font.family.serif; a mono-led system can point them at font.family.mono. The role name stays stable.
| Role | Default mapping | Usage |
|---|---|---|
font.role.body | Document and long-form body text | |
font.role.heading | Section and content headings | |
font.role.display | Large display text | |
font.role.ui | Controls, labels, navigation, compact UI | |
font.role.code | Code-like values, token names, technical text |
The shared primitive ladder below. Base ("md") is fixed at 1rem — the browser's own normal size — steps above compound by a ratio, steps below drop by a flat amount. Change either in build_font_scale() (scripts/scaffold_design_system.py) and every role that references it moves with it.
Grouped by role below. Each role aliases the ladder above rather than being consumed directly, so any one role can be resized on its own — button-sm and label start at the same value today, but changing one won't move the other.
Size and weight compound so hierarchy still reads if color changes. Map these to whichever heading tag is right for the document outline, not necessarily h1/h2/h3 literally.
| Example | Token | Size | Line height | Weight | Tracking | Usage |
|---|---|---|---|---|---|---|
| Heading text | ds-text-display | font.weight.display | tighter | Hero headlines, above any heading | ||
| Heading text | ds-text-heading-3xl | font.weight.heading-3xl | tighter | Large marketing headlines | ||
| Heading text | ds-text-heading-2xl | font.weight.heading-2xl | tight | Main page heading | ||
| Heading text | ds-text-heading-xl | font.weight.heading-xl | tight | Section headings | ||
| Heading text | ds-text-heading-lg | font.weight.heading-lg | tight | Subsection headings |
Normal to relaxed line height since paragraphs wrap across multiple lines, unlike headings or controls. copy-md is the browser's own default (1rem) — call it normal, not large.
| Example | Token | Size | Line height | Weight | Tracking | Usage |
|---|---|---|---|---|---|---|
| Copy text | ds-text-copy-lg | font.weight.copy-lg | normal | Lead paragraphs, dialogs | ||
| Copy text | ds-text-copy-md | font.weight.copy-md | normal | Default body text, the browser's own baseline | ||
| Copy text | ds-text-copy-sm | font.weight.copy-sm | normal | Secondary or dense body text | ||
| Copy text | ds-text-caption | font.weight.caption | normal | Helper text, metadata, field help/error |
Own semantic tokens, not reused from Body copy — resizing buttons never moves paragraph text. The roles follow the Button size names: button-sm and button-md intentionally share 14px, while button-lg steps to 16px. Line height is always none, since control text never wraps.
| Example | Token | Size | Line height | Weight | Tracking | Usage |
|---|---|---|---|---|---|---|
ds-text-button-lg | font.weight.button-lg | normal | Large Button · 40px; large control labels | |||
ds-text-button-md | font.weight.button-md | normal | Default Button, Input, and Select trigger; Command Menu search · 14px | |||
ds-text-button-sm | font.weight.button-sm | normal | Small Button · 32px; compact control labels |
Short single-line naming text, medium weight, and wide tracking. ds-text-label serves field labels; ds-text-label-sm serves compact group labels; ds-text-eyebrow shares the small recipe without forcing uppercase. A brand may add an uppercase transform, but the complete recipe remains intact.
| Example | Token | Size | Line height | Weight | Tracking | Usage |
|---|---|---|---|---|---|---|
| Label text | ds-text-label | font.weight.label | wide | Field labels | ||
| Label text | ds-text-label-sm | font.weight.label-sm | wide | Group labels — sidebar nav, menu, and command-list sections |
| Example | Token | Value | Usage |
|---|---|---|---|
| Aa | light | Optional display treatment; not assigned to a semantic recipe | |
| Aa | regular | Body's own default | |
| Aa | medium | Buttons, labels, tabs, table headers | |
| Aa | semibold | Headings, card/dialog titles | |
| Aa | bold | Not currently used |
Bands guide, semantic recipes decide. Display and heading roles take their band's tighter/tight tracking; body copy and controls pin normal; label and eyebrow recipes pin wide. The wider step is reserved for compact symbolic chords such as Menu keyboard shortcuts, where normal tracking visually fuses adjacent symbols.
| Example | Token | Value | Size range | Usage |
|---|---|---|---|---|
| Tracking | tighter | 2.5rem and up | Display and heading-3xl | |
| Tracking | tight | 1.25–2rem | Headings | |
| Tracking | normal | 1rem and down | Body text and all smaller UI text | |
| Tracking | wide | 0.875rem | Not currently used — available for label-size text | |
| ⇧⌘E | wider | 0.875rem | Compact symbolic chords — Menu keyboard shortcuts |