Semantic Aliases
Semantic aliases separate the physical color (e.g. --bg-warm-800) from its semantic role (e.g. --bg-text). This indirection is central to the maintainability of the design system: a theme refactor only changes the mapping in one place — not every component individually.
Why aliases?
If the theme should become warmer or cooler in 3 years, it's enough to change the mapping --bg-text → bg-warm-X in one place. If every component instead used var(--bg-warm-900) directly, a theme refactor would be a find-and-replace across the entire codebase. Aliases make the design system maintainable in the long run.
Brand Color Aliases
Brand neutrals as fixed reference points — derived from the Warm Gray Scale.
| Alias | Maps to | HEX | Usage |
|---|---|---|---|
--bg-brand-light | --bg-warm-50 | #F9F8F6 | Creamy surface background (light theme card) |
--bg-brand-dark | --bg-warm-800 | #3A3430 | Dark CTA buttons on orange background, footer, dark accents |
--bg-brand-black | --bg-warm-900 | #231F1C | Deepest brand neutral, print black, hover states for --bg-brand-dark |
→ Full specification: Brand Neutrals
Text Roles
Hierarchy of text visibility. All aliases are WCAG-validated against Brand White and Brand Light.
| Alias | Maps to | HEX | Usage | WCAG on white |
|---|---|---|---|---|
--bg-text | --bg-warm-900 | #231F1C | Headlines, primary body text | AAA (16.6:1) |
--bg-text-secondary | --bg-warm-600 | #6B635C | Lead text, secondary descriptions | AA (5.9:1) |
--bg-text-muted | --bg-warm-500 | #887F78 | Captions, metadata, timestamps | AA Large (3.9:1) |
--bg-text-disabled | --bg-warm-400 | #A69E97 | Disabled buttons, placeholders | — (2.6:1, decorative) |
Background Roles
| Alias | Maps to (Light) | Maps to (Dark) | Usage |
|---|---|---|---|
--bg-surface | #FFFFFF | --bg-warm-900 | Page background |
--bg-surface-subtle | --bg-warm-50 | --bg-warm-800 | Card background, hover states |
--bg-surface-muted | --bg-warm-100 | --bg-warm-700 | Tertiary surfaces, code block background |
Border Roles
| Alias | Maps to (Light) | Maps to (Dark) | Usage |
|---|---|---|---|
--bg-border | --bg-warm-200 | --bg-warm-700 | Standard hairline (cards, tables, form inputs) |
--bg-border-strong | --bg-warm-300 | --bg-warm-600 | Emphasized dividers, active form inputs, focus outlines |
Mapping Overview
┌─────────────────────────┐
│ Physical scale │
│ (Warm Gray) │
└─────────────────────────┘
│
┌──────────┴──────────────┐
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ Brand aliases │ │ Role aliases │
│ --bg-brand-light │ │ --bg-text │
│ --bg-brand-dark │ │ --bg-surface-subtle│
│ --bg-brand-black │ │ --bg-border │
└────────────────────┘ └────────────────────┘
│ │
└──────────┬──────────────┘
▼
┌─────────────────────────┐
│ UI components │
│ (use aliases, │
│ never scales direct) │
└─────────────────────────┘CSS Custom Properties
:root {
/* Brand aliases */
--bg-brand-light: var(--bg-warm-50);
--bg-brand-dark: var(--bg-warm-800);
--bg-brand-black: var(--bg-warm-900);
/* Text roles */
--bg-text: var(--bg-warm-900);
--bg-text-secondary: var(--bg-warm-600);
--bg-text-muted: var(--bg-warm-500);
--bg-text-disabled: var(--bg-warm-400);
/* Background roles — light theme */
--bg-surface: #FFFFFF;
--bg-surface-subtle: var(--bg-warm-50);
--bg-surface-muted: var(--bg-warm-100);
/* Border roles */
--bg-border: var(--bg-warm-200);
--bg-border-strong: var(--bg-warm-300);
}
[data-theme="dark"] {
--bg-surface: var(--bg-warm-900);
--bg-surface-subtle: var(--bg-warm-800);
--bg-surface-muted: var(--bg-warm-700);
--bg-border: var(--bg-warm-700);
--bg-border-strong: var(--bg-warm-600);
}Application Rules
- Components use aliases, never scale tokens directly.
color: var(--bg-text)instead ofcolor: var(--bg-warm-900). - Aliases are the single source of truth per role. A component should always use
--bg-textfor "body text", never alternate between--bg-warm-800and--bg-warm-900. - Theme switching happens at the alias level only. Dark mode changes the mapping
--bg-text → bg-warm-100, not every component. - Scale tokens are allowed for special cases where the semantic role isn't clear (e.g. decorative gradients, data visualizations with their own hierarchy).
Anti-Pattern
/* WRONG — couples the component directly to the scale */
.card-title { color: var(--bg-warm-900); }
/* RIGHT — uses the semantic role */
.card-title { color: var(--bg-text); }During a theme refactor, the second snippet doesn't need to be touched — the first one does.