Skip to content
ColorArchive
Design Systems Guide
Search intent: design token color system guide designers

How to Build a Color Token System: The Complete Designer's Guide

A color token system is the infrastructure layer between your abstract brand palette and the code that implements it. Tokens translate color decisions into reusable, maintainable variables that can be updated globally, themed, and exported to any platform. Building a token system is not about adding complexity — it is about removing the hidden complexity that accumulates when colors are hard-coded directly into components.

Design SystemsTokensColor Systems
Key points
A color token system has three tiers, and the distinction between tiers is the single most important concept in token architecture. Tier 1 (Primitive / Global tokens): the raw color values from your palette — `--color-blue-500: #3B82F6`. These tokens have no semantic meaning; they just name colors. They are never used directly in component code. Tier 2 (Semantic / Alias tokens): tokens that express intent rather than value — `--color-action-primary: var(--color-blue-500)`. These are the tokens components actually reference. Semantic tokens are what makes theming possible: to switch from blue to green primary, you change one semantic token, not hundreds of component-level hard-codes. Tier 3 (Component tokens, optional): component-specific tokens for large design systems — `--button-primary-background: var(--color-action-primary)`. Most projects do not need Tier 3 initially; add it when component-level overrides become necessary. The common mistake is to use only Tier 1 (raw hex) in components, which produces fragile systems that break on any rebrand.
Token naming is the decision with the longest-lasting consequences. Two naming philosophies exist: semantic naming (names express use) and descriptive naming (names express appearance). Semantic: `--color-text-primary`, `--color-surface-secondary`, `--color-feedback-error`. Descriptive: `--color-neutral-900`, `--color-brand-blue`, `--color-red-600`. Best practice: use descriptive naming at Tier 1 and semantic naming at Tier 2. Do NOT use color names that encode visual values into semantic positions (avoid `--color-primary-blue` because it breaks when the primary becomes green; prefer `--color-brand-primary` or `--color-action-interactive`). Do NOT name tokens for current values — `--color-dark-gray-text` creates problems when dark mode makes that 'dark gray' appear light. Name tokens for their role, not their current value.
Multi-theme token systems — supporting light mode, dark mode, and potentially brand variants — require that Tier 2 semantic tokens change their resolved Tier 1 value depending on the active theme, while component code remains unchanged. Implementation: define semantic tokens in a `:root` block for light mode, and override them in a `[data-theme='dark']` or `@media (prefers-color-scheme: dark)` block. Component code uses only semantic tokens — `background: var(--color-surface-primary)` — and automatically picks up the correct value for the active theme. The number of semantic tokens in a well-structured system is typically 30-60 for a complete product UI; a system with over 100 semantic tokens may have introduced unnecessary token proliferation.

Choosing your scale: how many steps do you need?

The standard 11-step scale (50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950) provides sufficient resolution for almost all UI use cases. Steps 50-200 are used for subtle tinted backgrounds, hover states, and light surface colors. Steps 300-400 are used for borders, dividers, and medium-emphasis surfaces. Step 500 is typically the base or identity color — the color that appears in the brand's identity, the primary button, or the logo. Steps 600-700 are used for hover states on primary actions and for icon colors on light backgrounds. Steps 800-900 are used for high-emphasis text on colored backgrounds and very dark surfaces. Step 950 is used for near-black text color and the darkest surface. Not every design needs all 11 steps — a minimal project might use only 50, 500, and 700 — but having the full scale available prevents the need to add ad-hoc color values later.

From primitives to semantics: mapping your palette to roles

Once your primitive scale exists, the next step is mapping primitive tokens to semantic roles. The core semantic roles most products need: `color-surface-primary` (page background, typically step 50 in light mode, step 950 or near-black in dark mode), `color-surface-secondary` (card/panel background, typically step 100 in light mode), `color-border-default` (dividers and outlines, typically step 200 in light mode), `color-text-primary` (body text, typically step 900 in light mode), `color-text-secondary` (metadata and supporting text, typically step 500 in light mode), `color-action-primary` (interactive elements, typically step 500 or 600 depending on contrast requirements), `color-action-primary-hover` (hover state, typically step 700). Semantic tokens for dark mode typically invert the scale: `color-text-primary` points to step 50 (near-white) instead of step 900 (near-black). Work through every distinct visual role in your product and assign it a semantic token before writing any component code.

Exporting tokens: CSS, Tailwind, JSON, and SCSS

Tokens need to be exported in a format that your development stack can consume. CSS custom properties are the most universal: they work in any web context, support runtime theming via JavaScript, and are referenced in any CSS value position. Tailwind CSS config uses a JavaScript object structure where color names map to hex values or CSS variable references; using CSS variables as the values in Tailwind config (`brand: { 500: 'var(--color-brand-500)' }`) allows runtime theming to work even with Tailwind's utility class approach. JSON in the W3C Design Token Community Group format (`{ '$value': '#hex', '$type': 'color' }`) is the most portable format — it is the input format for Style Dictionary, which can transform tokens into any platform's native format (iOS Swift, Android XML, CSS, Tailwind, SCSS). SCSS variables work for codebases that use Sass preprocessing and prefer variable-style token references over CSS custom properties.

Practical next step

Move from the guide into a concrete palette lane

Guides explain the use case. Collections prove the taste. Packs handle the export and implementation layer.

Related guides