The Colour System
A palette you have to remember is a palette you will break.
Picture the day it matters. Someone walks over: “can we change the blue?” If that blue lives in your head and in ninety copy-pasted hex codes, you lose the afternoon to find-and-replace — and still ship one you missed. If it lives in a system, you change one line and the whole product turns.
That’s the shift here: you stop painting screens and start naming decisions. Give each colour a job. Grow a family from one hue. Make dark mode a switch, not a second codebase. Three small labs below, and it all lands faster in your hands than on the page — so go and play.
- Grow one colour into a whole usable family — and see why the modern method looks smoother than the old one.
- Name colours for their job, not their look — so “the main button colour” can change without a hunt through your code.
- Flip one switch and watch every button, link and badge move together.
- Ship a real dark mode from that same set of names — a careful re-map, never a jarring invert.
Generate a ramp
A system doesn’t start with five colours — it starts with a ramp: one hue stepped from near-white to near-black, ten tints and shades that all obviously belong. The light steps become backgrounds and hovers; the dark steps become text and borders. One hue, a dozen jobs.
Pick a hue and read the tokens below. Then flip the colour space: in OKLCH every step looks evenly spaced; in HSL, the same even numbers bunch and lurch. That’s the whole reason systems moved to OKLCH.
Change one token
This is the move that turns a palette into a system. Give a colour a nickname for its job — call it “the action colour”. (In CSS you write that as --action; the double dash is just how the syntax works — read it as a name you invented.) Now every button, link and badge asks for the action colour instead of naming a specific blue.
Here’s the payoff: change what the action colour is, and all of them follow at once — because none of them ever knew it was blue. It’s far easier felt than read, so go feel it. The lab is one square and one instruction.
#3b82f6 typed into 5 rules, frozen
You just watched --action move the whole UI — so which name scales? Each token below is named for what it looks like. Tap to reveal the name that says what it does.
Flip light and dark
Dark mode isn’t an inversion — invert a light theme and you get glaring text, muddy shadows and buzzing accents. It’s a remap: the same semantic tokens (--bg, --surface, --text, --accent) point at different values in each theme, and the components never change at all.
Flip the toggle. The card, text and button stay exactly the same markup — only their tokens are reassigned. Notice the accent’s chroma is eased in dark mode so it doesn’t vibrate against the near-black.
State colours, one value
Success, warning, danger, info — the four colours a system uses most. Picked one at a time they drift to wildly different lightness, so the UI jumps in brightness as states change and a warning can shout louder than a danger. Generate them at a matched value and they differ only by meaning.
The one distinction that holds it together
Ramps, tokens and themes all rest on a single idea. Get it and a colour system almost builds itself; miss it and the tokens become just longer names for the same mess.
Primitives describe appearance. Semantic tokens describe intention.
A primitive is a raw colour with a literal name: --blue-600, --neutral-100. It says what the colour is. A semantic token has a job name: --action-primary, --text-muted, --surface. It says what the colour is for. Components should only ever reference the semantic layer — a button uses --action-primary, never --blue-600 — because then the meaning of “primary action” can move from blue-600 to blue-500, or to a completely different hue, without touching a single component.
There’s a third layer above those two: a component token — where a decision actually lands on screen. Three layers, one line worth memorising: --blue-600 is a colour. --action-primary is a decision. The button’s background is where the decision appears. The paint shelf, the job title, and the actor on stage — and the whole mistake is stopping at the paint shelf.
This one seam is what makes everything else possible. Theming is just pointing the semantic tokens at different primitives per mode. A rebrand is re-pointing them once. Accessibility is baked in when the semantic pairs (text on surface) are chosen to pass contrast by default. And a rename audit stops being terrifying, because the colour lives in exactly one place.
So the rule is short and strict: generate primitives as ramps, define semantic tokens on top of them, and let components speak only semantics. Name by role, not by hue — and the system survives every change you throw at it.
Repair this hardcoded palette
A component with the same blue typed in five times and a grey typed in four. Walk it into a system — lift the raw colours into primitives, then wire the components to semantic tokens.
Which is built to scale?
Five face-offs. Two ways to do the same thing — one survives change, one breaks under it. Pick the stronger, then see the verdict.
When the system paid off
Three teams learned the value of tokens the hard way — usually one rebrand or one dark-mode request too late. Hit greyscale test to see the structure a good ramp gives you.
The overnight rebrand
A startup switched its brand hue the night before a launch. The team with semantic tokens changed one primitive and shipped; the team with hardcoded blues spent the night hunting hexes and still missed a few.
The dark-mode request
“Can we add dark mode?” broke a product with colours baked into components — weeks of work. A token-based sibling shipped it in a day: one extra theme block re-pointing the same semantic names.
The mismatched states
Success, warning, danger and info were picked ad hoc, at wildly different lightness — the UI flickered as states changed. Regenerating all four at one matched value made state changes feel calm and consistent.
State colours, one value
The mark of a system: the four semantic state colours are different hues but the same perceived value, so switching between them never jumps in brightness. Generated, not hand-picked.
Four hues, one lightness. Because the value is held constant, no state feels louder than another — and each still has a light tint for its background.
Base, surface, text and a chroma-eased accent — identical semantic names, different primitives. One source of truth, two skins.
The system is where every earlier module gets encoded. The palette becomes primitives; the roles become semantic tokens; value defines the ramp steps; contrast floors are baked into the semantic pairings so accessible-by-default is the starting point. All that remains is the human layer — what these colours mean to people — which is the next module, before the capstone ties everything together.
Your starter system
The whole module on one clipboard: an OKLCH primitive ramp, semantic tokens named by role, and a light/dark theme map. Copy it as the seed for your next project — and for the capstone.
The System glossary
Six words that turn “the colours are a mess” into a fixable structure. Tap a card to flip it.
Questions you actually ask
What people get wrong
Build the system
Ten questions in seven formats, tagged by thinking level — each with layered hints: a nudge, the reasoning, then a deeper connection. A wrong answer opens a door, never a dead end. Served from the question bank, never hardcoded.