Color Hub Guides CSS Color Variables: Build a Scalable Design Token System

CSS & Dev

CSS Color Variables: Build a Scalable Design Token System

Learn how to use CSS custom properties for colors, structure a design token system, implement dark mode, and maintain consistency at scale.

⏱ 7 min read · Design Guide
Key Takeaways
  • Define once in :root, reference everywhere with var()
  • Use three layers: primitive → semantic → component tokens
  • Dark mode is one @media or data-theme swap away
  • Never encode hex values in token names

The Basics

CSS custom properties (CSS variables) are defined with a double-dash prefix and used via var(). They cascade like any CSS property and update live when changed via JavaScript:

:root {
  --color-brand: #4f6ef7;
  --color-text:  #1a1a1a;
  --color-bg:    #ffffff;
}

button {
  background: var(--color-brand);
  color: var(--color-text);
}

A Three-Layer Token System

Raw hex values are primitives. Semantic tokens are what makes a system maintainable:

  • Primitive tokens — Actual values: --blue-500: #3b82f6
  • Semantic tokens — Roles mapped to primitives: --color-action: var(--blue-500)
  • Component tokens — Specific usage: --button-bg: var(--color-action)

This three-layer approach lets you swap an entire theme by reassigning semantic tokens — without touching any components.

Dark Mode with CSS Variables

:root {
  --bg:   #ffffff;
  --text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg:   #111111;
    --text: #f5f5f5;
  }
}

Or use a data-theme attribute for user-toggled switching:

[data-theme="dark"] { --bg: #111; --text: #f5f5f5; }

Naming Conventions

Consistent naming prevents team confusion. Common patterns:

  • --color-[role]-[variant] — e.g., --color-surface-raised
  • --[component]-[property] — e.g., --button-border-radius
  • Scale-based: --gray-100 through --gray-900
Avoid This

Don't encode the hex in the name (--blue-color). When you change the value, the name becomes misleading.

Fallback Values

var() accepts an optional fallback: var(--color-brand, #4f6ef7). Use this in component libraries so they work even if the consuming project hasn't defined all tokens.

Export Workflow

Define your palette in a design tool → export to CSS variables → treat that file as single source of truth. React, Vue, Svelte, and plain HTML all consume the same token file.

Export your palette as CSS variables

Generate production-ready CSS custom properties with roles and dark mode

Export CSS →