design hubs
On this page
Application
5 min read

Systematic Colour

A colour system replaces ad-hoc decisions with a structured scale — semantic tokens built on a base palette that apply consistently regardless of who writes the code.

Every interface makes colour decisions. The question is whether those decisions are made once — at the system level — or made repeatedly at the moment of implementation. Without a system, each developer and designer reaches for colours independently: “I’ll use #1e293b here,” “I’ll use a darker blue for this hover,” “I’ll make this border light grey.” The result is a palette that drifts, contradicts itself, and becomes inconsistent as the product scales. A colour system makes the correct choice the path of least resistance.

The base scale

A base scale is a family of values for a single hue, spanning from near-white to near-black in clearly graduated steps. Each step has a name — typically a number like 50, 100, 200 through 950 — so it can be referenced consistently. Within the scale, adjacent steps have a perceptible but controlled lightness difference; the scale is designed so that any step is clearly distinguishable from its neighbours but harmonious with the whole family.

Tailwind CSS popularized this approach with its colour scales (slate-50 through slate-950, with each hue having 11 steps). The advantage is that “slate-700 on slate-100” is a communicable, consistent decision: anyone implementing the design reaches for the same values. The alternative — “use a dark grey text on a light grey background” — produces different values every time it’s implemented.

Most design systems define 2–4 base scales: a neutral scale for the primary grey family, a brand scale for the primary brand hue, and additional scales for semantic states (red, green, amber). Everything in the interface is a value from one of these scales.

Semantic tokens

A base scale answers “what colours exist?” A semantic token answers “what colour is used for what purpose?” Tokens are names like --colour-text-primary, --colour-surface, --colour-accent, --colour-danger — each one points to a value in the base scale. In CSS, this is CSS custom properties:

:root {
  --colour-surface: var(--slate-50);
  --colour-text-primary: var(--slate-900);
  --colour-accent: var(--amber-600);
  --colour-danger: var(--red-600);
}

.dark {
  --colour-surface: var(--slate-900);
  --colour-text-primary: var(--slate-100);
  --colour-accent: var(--amber-400);
  --colour-danger: var(--red-400);
}

The component code uses var(--colour-accent), not var(--amber-600). This separation means dark mode is just a theme override that changes what the tokens point to — the component code doesn’t change.

Separating definition from use

The architectural principle behind semantic tokens is the separation of definition from use. The base scale defines what colours exist. The semantic layer defines what they mean. The component layer uses semantic meanings, never base scale values directly.

This means that changing your brand colour is a one-line change in the semantic layer, not a find-and-replace across thousands of component files. It means dark mode is a theme variant, not a parallel design. It means a new surface type (elevated modal, status banner) inherits from existing semantic tokens rather than introducing new raw values.

The rule: every colour used in a component should have a semantic name. If you’re about to use var(--slate-300) directly in a component, stop and ask what semantic role that colour is playing — border, divider, placeholder, disabled text — and define a token for that role. The discipline of naming is the discipline of design decisions.

Implementing in CSS

In CSS, the implementation is @property for type-safe tokens (supported in modern browsers), or CSS custom properties with documentation. Define your base scale at :root, define semantic tokens at :root and .dark, and use only semantic tokens in component styles:

:root {
  /* Base scale */
  --slate-50: #f8fafc;
  --slate-900: #0f172a;
  --amber-400: #fbbf24;
  --amber-600: #d97706;

  /* Semantic tokens — point into the scale */
  --colour-bg: var(--slate-50);
  --colour-text: var(--slate-900);
  --colour-accent: var(--amber-600);
}

.dark {
  --colour-bg: var(--slate-900);
  --colour-text: var(--slate-50);
  --colour-accent: var(--amber-400);
}

As your system grows, the semantic layer grows to cover every distinct colour role in your interface. The base scale grows only when a genuinely new hue family is needed. In practice, most mature design systems have 3–5 base scales and 20–40 semantic tokens — a manageable set of decisions that governs every colour in the product.

Mapping brand colour to a UI system

Brand colour systems and UI colour systems have different requirements, and mapping between them is a common real-world task. A brand palette typically defines one to three hero colours chosen for visual impact on marketing materials. A UI colour system needs far more: surface colours, text colours, semantic states (success, warning, error), interactive states, and dark mode variants.

Check the brand colour for accessibility first. Brand colours are often chosen for visual impact, not contrast. A vivid brand orange at 3.1:1 on white fails WCAG AA for text. You will likely need a darkened variant for text use (--colour-brand-text) while keeping the original for non-text brand moments (--colour-brand-fill).

Extract a neutral family from the brand hue. Use the brand hue as the tint direction for your neutral scale. A warm brand colour becomes warm-tinted near-blacks and near-whites (hsl(30, 8%, 10%) rather than hsl(220, 8%, 10%)). This creates brand cohesion across the interface without using the brand colour on every element.

Define semantic colours independently. Error red, success green, and warning amber are chosen for clarity and accessibility, not brand alignment. They can be adjusted to harmonise with the brand palette, but functional legibility comes first.

Document every brand-to-UI decision. Which brand colour maps to which semantic token, which variant is used where, and why. This documentation prevents the drift that occurs when developers reach for the raw brand colour instead of the semantic token — and it makes brand refreshes tractable, since the mapping is explicit.

Practice

0 / 3

Keyboard shortcuts

Show shortcuts
?
Search
CtrlK
Previous article
Next article
Close
Esc