Semantic Color Tokens — Role-Based Color Naming

Why naming colors by their role beats naming them by hue — and how to structure a semantic token system that scales.

The Problem With Hue-Based Names

Most design systems start with hue-based names: --blue, --red, --green. This seems natural — blue is blue, red is red. But it creates a cascade of problems:

  • Rebranding breaks everything: If you rename the primary brand color from blue to purple, every reference to --blue must change
  • Dark mode is impossible: Blue at night is not the same as blue in daylight — you need different values, not just hue names
  • Semantic meaning is lost: "Use --blue" doesn't tell you why you are using blue
  • Multiple blues conflict: Are you referring to the brand blue, the link blue, the info blue?

What Semantic Naming Solves

Semantic tokens are named by role: --color-primary, --color-text, --color-danger. This means:

  • Change is centralized: Redefine --color-primary once, everything updates
  • Theming works: Override semantic tokens for dark mode, brand variants, white-labeling
  • Intent is clear: "Use --color-primary" tells you it's a primary action
  • No ambiguity: One token, one meaning, one use

The Token Hierarchy

Primitives (Values)

Raw values: --blue-500: #155EEF. Never used directly in components.

Semantics (Intent)

Role-based: --color-primary: var(--blue-500). What components use.

Components (Scoping)

Optional: --button-bg: var(--color-primary). Used for component-specific overrides.

Standard Semantic Token Categories

Brand Tokens

  • --color-primary
  • --color-secondary
  • --color-accent

Interactive State Tokens

  • --color-primary-hover
  • --color-primary-active
  • --color-primary-focus
  • --color-primary-disabled

Surface Tokens

  • --color-background
  • --color-surface
  • --color-surface-elevated
  • --color-border

Text Tokens

  • --color-text
  • --color-text-muted
  • --color-text-subtle
  • --color-text-inverse

System State Tokens

  • --color-success + --color-success-light
  • --color-warning + --color-warning-light
  • --color-danger + --color-danger-light
  • --color-info + --color-info-light

Naming Rules

  1. Role first: color-text not text-color
  2. Consistent structure: --{category}-{role}-{variant}
  3. No hue names: Never --blue-button
  4. No camelCase: Kebab-case for CSS compatibility
  5. No numbers except scale: --color-primary not --color-primary-2

Theming With Semantic Tokens

The biggest benefit: theming becomes trivial. To add dark mode, override semantic tokens without touching primitives or components:

[data-theme="dark"] {
  --color-background: #0F172A;
  --color-surface: #1A2437;
  --color-text: #F1F5F9;
  --color-border: #2D3A52;
}

/* Components automatically inherit the new colors */

Common Mistakes

  • Creating too many tokens: Every component gets its own tokens — creates a maintenance nightmare
  • Using primitives directly: background: var(--blue-500) bypasses theming
  • Inconsistent naming: Mixing --color-primary and --primary-color
  • Forgetting dark mode: Semantic tokens without dark overrides fail when you need to add theming
  • Reusing tokens for different meanings: Using --color-primary for both brand and success undermines semantics

Building Your Semantic System

  1. Audit your current color usage
  2. Identify semantic roles (what each color communicates)
  3. Map primitives to semantic tokens
  4. Document the mapping
  5. Refactor components to use semantic tokens only
  6. Set up theming via semantic overrides

Generate Your Tokens

Use our CSS Variables Generator to auto-generate 10 semantic tokens from any base color.

Frequently Asked Questions

Related Articles