Semantic Aliases
Semantic aliases separate the physical color (e.g. --warm-800) from its semantic role (e.g. --text-primary). 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 --text-primary → warm-X in one place. If every component instead used var(--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 |
|---|---|---|---|
--brand-light | --warm-50 | #F9F8F6 | Creamy surface background (light theme card) |
--brand-dark | --warm-800 | #3A3430 | Dark CTA buttons on orange background, footer, dark accents |
--brand-black | --warm-900 | #231F1C | Deepest brand neutral, print black, hover states for --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 |
|---|---|---|---|---|
--text-primary | --warm-900 | #231F1C | Headlines, primary body text | AAA (16.6:1) |
--text-secondary | --warm-600 | #6B635C | Lead text, secondary descriptions | AA (5.9:1) |
--text-muted | --warm-500 | #887F78 | Captions, metadata, timestamps | AA Large (3.9:1) |
--text-disabled | --warm-400 | #A69E97 | Disabled buttons, placeholders | — (2.6:1, decorative) |
Background Roles
| Alias | Maps to (Light) | Maps to (Dark) | Usage |
|---|---|---|---|
--bg-primary | #FFFFFF | --warm-900 | Page background |
--bg-subtle | --warm-50 | --warm-800 | Card background, hover states |
--bg-muted | --warm-100 | --warm-700 | Tertiary surfaces, code block background |
Border Roles
| Alias | Maps to (Light) | Maps to (Dark) | Usage |
|---|---|---|---|
--border | --warm-200 | --warm-700 | Standard hairline (cards, tables, form inputs) |
--border-strong | --warm-300 | --warm-600 | Emphasized dividers, active form inputs, focus outlines |
Mapping Overview
┌─────────────────────────┐
│ Physical scale │
│ (Warm Gray) │
└─────────────────────────┘
│
┌──────────┴──────────────┐
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ Brand aliases │ │ Role aliases │
│ --brand-light │ │ --text-primary │
│ --brand-dark │ │ --bg-subtle │
│ --brand-black │ │ --border │
└────────────────────┘ └────────────────────┘
│ │
└──────────┬──────────────┘
▼
┌─────────────────────────┐
│ UI components │
│ (use aliases, │
│ never scales direct) │
└─────────────────────────┘CSS Custom Properties
:root {
/* Brand aliases */
--brand-light: var(--warm-50);
--brand-dark: var(--warm-800);
--brand-black: var(--warm-900);
/* Text roles */
--text-primary: var(--warm-900);
--text-secondary: var(--warm-600);
--text-muted: var(--warm-500);
--text-disabled: var(--warm-400);
/* Background roles — light theme */
--bg-primary: #FFFFFF;
--bg-subtle: var(--warm-50);
--bg-muted: var(--warm-100);
/* Border roles */
--border: var(--warm-200);
--border-strong: var(--warm-300);
}
[data-theme="dark"] {
--bg-primary: var(--warm-900);
--bg-subtle: var(--warm-800);
--bg-muted: var(--warm-700);
--border: var(--warm-700);
--border-strong: var(--warm-600);
}Application Rules
- Components use aliases, never scale tokens directly.
color: var(--text-primary)instead ofcolor: var(--warm-900). - Aliases are the single source of truth per role. A component should always use
--text-primaryfor "body text", never alternate between--warm-800and--warm-900. - Theme switching happens at the alias level only. Dark mode changes the mapping
--text-primary → 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(--warm-900); }
/* RIGHT — uses the semantic role */
.card-title { color: var(--text-primary); }During a theme refactor, the second snippet doesn't need to be touched — the first one does.