How to Build a Color System With Design Tokens

A color system should make the right color obvious to pick and the wrong one hard to reach for.

How to Build a Color System With Design Tokens — Troiana insight cover

In short

Build a color system as a numbered scale per hue (e.g. blue-100 to blue-900), map semantic names (text, surface, border, danger) onto specific scale steps, and design the scale so equivalent semantic steps in light and dark mode maintain the same contrast relationships.

Start with scales, not one-off colors

A color system built from individual named colors ("brand blue," "error red") runs out of flexibility fast — there's no lighter or darker variant when you need one for a hover state or a subtle background. Build each hue as a numbered scale instead: blue-100 (lightest) through blue-900 (darkest), with enough steps (typically 9-10) to cover text, backgrounds, borders, and interactive states without inventing new colors later.

Generating a scale that actually looks right

Rather than picking each step by eye, generate the scale programmatically from a base color using consistent lightness/chroma steps in a perceptually uniform color space (like OKLCH or LCH) rather than plain HSL — this produces scales that look evenly spaced to the eye, which naive HSL interpolation often doesn't.

Layering semantic names on top

Raw scale values (blue-600) shouldn't be used directly in components — map them to semantic tokens that describe purpose: color-action-primary, color-text-secondary, color-surface-danger, color-border-subtle. This is the layer components actually reference, and it's what lets you re-theme (including for dark mode) by remapping semantic tokens without touching a single component.

Designing for dark mode from the start

The cheapest way to support dark mode is to design the semantic layer so light and dark are just two different mappings onto the same scale logic — color-surface-primary might be gray-50 in light mode and gray-900 in dark mode, but both represent "the primary background" conceptually. Building this mapping in from the start is far less work than retrofitting dark mode onto a system that only ever had light-mode semantics.

Building in accessible contrast

Decide your minimum contrast ratio (WCAG AA is 4.5:1 for normal text, 3:1 for large text) at the scale-design stage, not per-component later. If your scale is built so that, say, gray-900 text on gray-50 background always clears 4.5:1, every component that uses those semantic tokens is accessible by default — see how to create accessible color contrast for the specifics.

Handling brand colors that don't scale well

Some brand colors (a very saturated, mid-lightness color) don't have much room to get lighter or darker while staying recognizably "the brand color." In these cases, use the brand color for one specific role (a primary action color, an accent) rather than trying to force a full 9-step scale out of it — pair it with a neutral scale that does the structural work of text, surfaces, and borders.

A minimal viable color system

At minimum: one neutral scale (grays, for text/surfaces/borders), one or two accent scales (brand, plus maybe a secondary), and semantic status colors (success, warning, danger, info) each as their own small scale. This covers the vast majority of product UI needs without over-engineering.

Common mistakes

  • Skipping the semantic layer and using raw scale values in components — makes theming and dark mode much harder later.
  • Designing the scale in HSL without checking perceptual evenness — steps often look uneven even though the numbers are evenly spaced.
  • Not testing contrast until QA — bake the ratio requirement into the scale itself so it can't be violated by accident.

More from our insights: Building a Design System That Scales.

Common questions

How many steps should a color scale have?

9-10 steps per hue is a common sweet spot — enough to cover text, background, border, and hover/active states without needing to invent extra values later.

Should I build dark mode as a separate color system?

No — design one semantic layer with light and dark mappings onto the same underlying scale logic, rather than maintaining two independent systems that can drift out of sync.

What color space should I use to generate the scale?

A perceptually uniform space like OKLCH produces more evenly-spaced-looking steps than plain HSL, which is worth the small extra tooling effort for a scale you'll use everywhere.

Have something worth building right?