30

Building a Color System

A handful of nice colors is not a color system. A system is a structure: tonal scales with predictable steps, semantic tokens that name roles instead of values, and a single source of truth that themes - light, dark, brand - all derive from. Get the structure right and a thousand-screen product stays consistent and accessible. This is the interactive guide to building one from a single base color.

Design · 36 4 Live Demos ~35 min read Tokens & scales
50-950
Standard scale steps
semantic
Tokens name roles, not values
2
Themes, one token map
AA
Every pair checked
01

Colors vs a color system

Picking a few colors you like is the easy part - and it does not scale. The moment a product has dozens of components, multiple states, and a dark mode, ad-hoc colors collapse into chaos: seventeen almost-identical grays, buttons that fail contrast on half the surfaces, a dark theme that is a frantic find-and-replace. A color system replaces all of that with structure - a small set of scales and a layer of named tokens - so every screen draws from the same well.

The payoff is consistency, accessibility, and changeability. Rebrand, retheme, or fix a contrast bug in one place and it propagates everywhere, because components never hard-code a color - they reference a role.

Three layers. A good system is built in layers: scales (the raw ramps), primitive tokens (named steps like blue-600), and semantic tokens (roles like color-surface). Components touch only the top layer; everything cascades down from the scales.
02

The tonal scale

The atom of a color system is the tonal scale: one hue rendered at a fixed ladder of lightness steps, conventionally numbered 50 (lightest) through 950 (darkest). Build it in a perceptual space like OKLCH so the steps feel evenly spaced and the hue stays put - a naive HSL ramp drifts in hue and bunches up in lightness. Pick a base and generate the whole ladder.

Interactive 01 · Tonal scale

One base color, a full 50-950 ramp

Choose a base color and tune the hue. The generator builds an 11-step OKLCH ramp with even perceptual lightness and consistent hue - the kind of scale a design system ships. Click any chip to copy its hex. Step 500-600 is your "base"; the rest are backgrounds, borders, and text.

 

03

Primitive vs semantic tokens

Scales are raw material; tokens are how the system is actually used. The trick is two levels. Primitive tokens name the raw steps - --blue-600, --neutral-100 - a stable vocabulary for the palette. Semantic tokens name roles - --color-surface, --color-text, --color-primary - and point at a primitive. Components reference only semantic tokens, never raw colors.

/* primitive */ --blue-600: #2f6bdb; --neutral-50: #f7f8fa;
/* semantic */ --color-primary: var(--blue-600); --color-surface: var(--neutral-50);
/* component */ background: var(--color-surface); color: var(--color-text); Three layers of indirection. To retheme or rebrand, you only ever repoint the middle layer - components and scales stay untouched.
Why the indirection pays off: a component that says var(--color-primary) doesn't care whether that's blue today and green after a rebrand, or step 600 in light mode and step 400 in dark. The role is stable; the value behind it is free to change.
04

Themes from one token map

A theme is just a different mapping of semantic tokens to scale steps. Light mode points surface at step 50 and text at 900; dark mode points surface at 900 and text at 100. The components never change - only the map flips. Watch the same UI re-theme below.

Interactive 02 · Token mapping

The same tokens, two themes

A mini interface wired entirely to semantic tokens, drawn from the scale you generated above. Flip the theme: the token-to-step mapping changes, the components do not. The list underneath shows which scale step each role resolves to in the current theme.

◆ Acme Home · Docs · Pricing

Project overview

Link →
05

Pairs that pass

A scale is only useful if you know which steps can safely sit on which. The contrast matrix below computes the WCAG ratio for every text-step on every background-step pairing in your scale - green where it clears AA (4.5:1) for normal text. It is the map that tells you "text-900 on surface-100: yes; text-500 on surface-300: no."

Interactive 03 · Contrast matrix

Every step against every step

Rows are text color (a scale step), columns are background (a scale step). A green cell passes WCAG AA for normal text (≥ 4.5:1); a dim cell fails. The pattern shows why text usually lives at the dark end and surfaces at the light end - and where the safe pairings are. Uses the scale from the first demo.

Green = passes AA (4.5:1) · dim = fails
06

Neutral, primary, accent

Real systems are not one scale but a small set. At minimum you want a neutral ramp (low chroma - for backgrounds, borders, and text), a primary ramp (the brand action color), and often an accent for highlights. Generate them with the same lightness ladder so they stay in lockstep, varying only hue and chroma.

Interactive 04 · The full system

Three scales, one ladder

A neutral, a primary, and an accent ramp, all sharing the same 50-950 lightness steps so a border, a button, and a highlight at the "same" level always agree. Tune the primary and accent hues; the neutral stays subtly tinted toward the primary for cohesion.

Neutral
Primary
Accent
07

State and feedback colors

Beyond brand and neutral, a system needs semantic state colors for feedback - and these carry meaning, so convention beats creativity. Each gets its own small scale and its own contrast-checked pairs (a light background tint plus a dark text/icon).

Success
Green by strong convention. Used for confirmations and valid states - pair a 50/100 tint background with a 700/800 text.
Warning
Amber/orange. Harder to make accessible - yellows need very dark text; never put white on a mid yellow.
Error / danger
Red, for destructive actions and failures. Reserve it; overuse drains its urgency.
Info
Blue, often the same family as primary. Distinguish it so "info" and "primary action" do not blur together.
Never color alone. State colors must be backed by an icon, label, or shape - red/green status is invisible to many colorblind users. The color is the accent on the meaning, not the meaning itself.
08

Shipping the tokens

The system lives as design tokens - a single, named source of truth that exports to every platform: CSS custom properties, Sass, Tailwind config, iOS/Android, Figma variables. Define them once; generate the rest. The semantic layer is where light/dark and brand variants are switched.

:root {
  --neutral-50:#f7f8fa; --neutral-900:#1a1d23; --blue-600:#2f6bdb; /* primitives */
  --color-surface: var(--neutral-50); --color-text: var(--neutral-900);
  --color-primary: var(--blue-600);
}
[data-theme="dark"] {
  --color-surface: var(--neutral-900); --color-text: var(--neutral-50);
} Components only ever read the semantic tokens. The Auric Artisan Shade and Palette tools generate these scales in OKLCH and export them as ready-made token sets.
09

Pitfalls and gotchas

Hard-coding hex
Raw colors scattered through components make rebrand and dark mode impossible. Always go through semantic tokens.
HSL ramps
Naive HSL scales drift in hue and bunch in lightness. Build ramps in OKLCH/Lab for even, predictable steps.
Too many grays
Seventeen ad-hoc neutrals is the classic smell. One neutral ramp, referenced by role, replaces them all.
Skipping the contrast pass
A pretty scale with failing pairs is unusable. Build the contrast matrix and only pair steps that pass.
Semantic tokens that name colors
--color-blue-button defeats the point. Name the role (--color-primary), not the color.
Dark mode as an afterthought
Bolting on dark later means re-checking everything. Design the token map for both themes from the start.
10

Test your understanding

Six questions on scales, tokens, themes, and contrast. Instant feedback, no scores recorded - a wrong answer comes with a short explanation.

Quick check

Loading…
 
Question 1 of 6
11

Continue your journey

A color system pulls together theory, perceptual scales, contrast, and theming. The numbers reflect each article's position in the editorial roadmap.