# Tokens Every visual decision in the system is a CSS custom property, a token. App CSS consumes tokens; it does not hardcode values. This is what makes rebranding, dark mode, and density adjustments one-line changes. ## The three-tier model Tokens are layered in three tiers, each building on the one below. **Tier 1: the scale (primitives).** `tokens/scale.css` defines the raw, non-color scales: the type ramp, weights, leading and tracking, the spacing scale, radius, shadows, motion, z-index layering, breakpoint reference values, and container widths. These are the vocabulary: `--space-4`, `--text-lg`, `--radius-md`. They don't change between themes. See [scale](scale). **Tier 2: the semantic theme.** `themes/default.css` defines the color ramps (one neutral + four status ramps in OKLCH) and then maps them onto **semantic** tokens: `--bg`, `--ink`, `--rule`, `--accent`, the status triples, focus rings, and the per-component slots. It also flips those semantic tokens for dark mode. This is the tier your app reads from. See [color](color) and [components](components). **Tier 3: the optional brand theme.** A theme file imported after the default repoints the accent block (and optionally a brand ramp) to color the whole system. `themes/elements.css` is the shipped example; `themes/customer-example.css` is the documented recipe. See [theming](../theming). ```text scale.css primitives --space-4, --text-lg, --radius-md ↓ themes/default semantic tokens --ink → --neutral-900, --accent → --neutral-900 ↓ brand theme repoint accent --accent → your brand ramp (optional) ``` ## Consume semantic tokens, not raw ramps The neutral and status ramps (`--neutral-500`, `--danger-600`, …) exist so the semantic tokens have something to point at. In app CSS, reach for the **semantic** token, not the ramp step: ```css /* Do this: follows the theme and dark mode automatically */ .notice { color: var(--ink-soft); background: var(--bg-soft); border: 1px solid var(--rule); } /* Not this: a raw ramp step won't flip in dark mode */ .notice { color: var(--neutral-700); background: var(--neutral-50); } ``` `--ink-soft` is `--neutral-700` in light mode and `--neutral-300` in dark mode. Point at the semantic name and both come free. Reach past it to the ramp and you opt out of theming. ## Where the tiers live | Tier | File | Layer | |---|---|---| | Scale primitives | `tokens/scale.css` | `tokens` | | Semantic theme + colors + dark mode | `themes/default.css` | `tokens` | | Brand theme (opt-in) | `themes/elements.css` | `tokens` | All three sit in `@layer tokens`, low in the cascade, so your app's own token overrides, placed later, always win. See [overriding](../overriding). - [color](color): the neutral and status ramps and every semantic color token. - [scale](scale): type, spacing, radius, shadow, motion, and the rest of the primitives. - [components](components): the per-component token slots.