--- name: theming-design-tokens description: Builds design-token systems with CSS custom properties — primitive/semantic/component tiers, dark mode via prefers-color-scheme with a data-theme override, spacing/radius/shadow scales, and contrast-safe color palettes. Use when the user asks to set up design tokens or CSS variables, add dark mode or theme switching, define a color palette or spacing scale, or make theme colors meet contrast requirements. Do not use for typeface selection and type scales (choosing-typography) or for one-off page styling that no system will reuse. --- # Theming with Design Tokens ## When to use / when NOT to use - **Use for:** token architecture, palettes, dark mode/theming, spacing/radius/shadow scales, migrating hard-coded values to tokens. - **Do NOT use for:** font pairing and type scales (→ choosing-typography); layout mechanics (→ designing-responsive-layouts); styling a single element nothing else will reuse — just style it. ## Core rules 1. **Three tiers, referenced downward only: primitive → semantic → component.** Components consume semantic tokens; semantic tokens reference primitives; nothing skips upward. - ✅ `--color-blue-600: #2563eb;` → `--color-accent: var(--color-blue-600);` → `--button-bg: var(--color-accent);` - ❌ `--button-bg: #2563eb;` (component pinned to a raw hex) 2. **Semantic names describe role, never appearance.** - ✅ `--color-bg-surface`, `--color-text-muted`, `--color-border-danger` - ❌ `--light-gray-2`, `--dark-blue-bg` (breaks the moment the theme flips) 3. **Dark mode = redefining semantic tokens only.** Primitives and components never change per theme. 4. **Both mechanisms, always:** `@media (prefers-color-scheme: dark)` for the default, `[data-theme="dark"]` / `[data-theme="light"]` for the user override; plus `color-scheme: light dark` on `:root` so form controls and scrollbars follow. 5. **Contrast is enforced in the token definitions, not per usage:** every `text`/`bg` semantic pair ≥ 4.5:1 (WCAG AA), every UI-component/border pair ≥ 3:1. Verify pairs when defining them — then usage is safe by construction. 6. **Scales, not ad-hoc values.** Spacing on a base-4/8 scale (`0.25/0.5/0.75/1/1.5/2/3/4rem`), 3–4 radii, 3 shadow levels. A new value must join the scale or justify itself. - ✅ `padding: var(--space-4) var(--space-6);` - ❌ `padding: 13px 22px;` 7. **Token count stays small:** ~10–20 semantic color tokens covers most products. If you have 60, roles are duplicated — merge before adding. ## Workflow 1. Define primitives: a 3–5 step neutral ramp + 1 accent ramp (+ success/warn/danger primitives), spacing/radius/shadow scales. 2. Define semantic tokens for both themes (rule 2–4), checking each text/bg pair's contrast ratio as you go (rule 5). 3. Wire components to semantic tokens only. 4. Add the theme toggle contract: `data-theme` attribute on ``, persisted; absence = follow system. 5. Validate: toggle both themes; confirm every visible pairing passes 4.5:1 (text) / 3:1 (UI); grep the stylesheet for stray hex/rgb values outside the primitives block — there should be none. ## Edge cases & failure modes - **Brand color fails contrast on light bg:** keep the brand primitive for large/decorative use; add a darkened `-text-safe` variant for text/small UI, and note the pair it passes against. - **Images/illustrations in dark mode:** don't invert; reduce brightness slightly (`filter: brightness(0.9)`) or provide themed assets. - **Shadows invisible on dark surfaces:** in dark themes, convey elevation with a subtly lighter surface token instead of larger shadows. - **Third-party widgets ignore tokens:** scope overrides in one `@layer third-party` block rather than scattering `!important`. - **Flash of wrong theme on load:** set `data-theme` in a tiny inline script in `` before CSS paints. ## References Full starter token sheet (light + dark), toggle script, and contrast-checked palette: see [references/patterns.md](references/patterns.md)