Skip to content

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.

AliasMaps toHEXUsage
--bg-brand-light--bg-warm-50#F9F8F6Creamy surface background (light theme card)
--bg-brand-dark--bg-warm-800#3A3430Dark CTA buttons on orange background, footer, dark accents
--bg-brand-black--bg-warm-900#231F1CDeepest 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.

AliasMaps toHEXUsageWCAG on white
--bg-text--bg-warm-900#231F1CHeadlines, primary body textAAA (16.6:1)
--bg-text-secondary--bg-warm-600#6B635CLead text, secondary descriptionsAA (5.9:1)
--bg-text-muted--bg-warm-500#887F78Captions, metadata, timestampsAA Large (3.9:1)
--bg-text-disabled--bg-warm-400#A69E97Disabled buttons, placeholders— (2.6:1, decorative)
Headline (text-primary)
Lead text with a bit more context (text-secondary).
Caption with metadata · 2026-05-06 · 3 min read (text-muted)
Disabled button text or placeholder (text-disabled)

Background Roles

AliasMaps to (Light)Maps to (Dark)Usage
--bg-surface#FFFFFF--bg-warm-900Page background
--bg-surface-subtle--bg-warm-50--bg-warm-800Card background, hover states
--bg-surface-muted--bg-warm-100--bg-warm-700Tertiary surfaces, code block background
bg-primary (#FFFFFF)
Standard page background
bg-subtle (Warm 50)
Card background, hover states, secondary sections
bg-muted (Warm 100)
Tertiary surfaces, code blocks, recessed areas

Border Roles

AliasMaps to (Light)Maps to (Dark)Usage
--bg-border--bg-warm-200--bg-warm-700Standard hairline (cards, tables, form inputs)
--bg-border-strong--bg-warm-300--bg-warm-600Emphasized dividers, active form inputs, focus outlines
Standard card
border (Warm 200) — the standard hairline
Active card
border-strong (Warm 300) — emphasized separation

Mapping Overview

text
                    ┌─────────────────────────┐
                    │   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

css
: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

  1. Components use aliases, never scale tokens directly.color: var(--bg-text) instead of color: var(--bg-warm-900).
  2. Aliases are the single source of truth per role. A component should always use --bg-text for "body text", never alternate between --bg-warm-800 and --bg-warm-900.
  3. Theme switching happens at the alias level only. Dark mode changes the mapping --bg-text → bg-warm-100, not every component.
  4. 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

css
/* 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.

Documentation licensed under CC BY-NC 4.0 · Code licensed under MIT