Why the colour wheel is not enough
Every designer learns the colour wheel — primary, secondary, complementary, analogous. It is a useful starting point for understanding how colours relate. But applying the wheel directly to product design produces consistently mediocre palettes. Real product palettes have to satisfy constraints the wheel doesn't address: legibility on small mobile screens, accessibility for low-vision users, dark-mode parity, and consistency across hundreds of dynamic UI states.
In this post we unpack the framework we use to design product colour systems. It is more practical than theoretical, and it is what we hand to engineers as the contract for a brand's colour identity.

Start with the role, not the hue
The most common mistake in product colour systems is starting with "what colour should the brand be?" That is a good question for marketing, but a bad starting point for product. The right starting point is: what role does each colour play in the interface?
Every product UI has the same handful of colour roles. A small palette covers all of them:
Foreground (text, icons): the highest-contrast colour in the system, used for content the user must read.
Background (page surfaces, cards): the canvas. Usually a near-white in light mode and a near-black in dark mode, but never quite either — pure white and pure black are visually exhausting at scale.
Primary (the main brand colour, used for principal actions): the colour the user looks at when they need to decide what the next step is.
Secondary (supporting actions, accents): used when there are two viable actions and the primary is reserved for the most important one.
Semantic (success, warning, error, info): culturally loaded colours with universal meaning. These are usually green, yellow, red, blue — but the exact shade should belong to your brand.
Neutral (borders, dividers, disabled states, secondary text): the unsung hero of any UI palette. Most production UIs have eight to twelve neutrals, each occupying a specific role on the contrast ladder.
You can ship a beautiful product with a five-colour palette plus a neutral ramp. You cannot ship a beautiful product with twelve colours that have no defined roles.
Build ramps, not points
Once you have decided on roles, each colour becomes a ramp — a series of related shades from very light to very dark. The ramp is what gives you flexibility across contexts. A "primary blue" with twelve stops on the ramp is a hundred times more useful than a single hex code, because every UI state — hover, active, focus, pressed, selected — needs a slightly different shade of the same hue.
We typically build ramps with twelve stops, named 50 through 950 in increments. The 500 stop is the "true" colour; everything below is lighter, everything above is darker. Each adjacent pair has a deliberate contrast ratio, so going up the ramp gives you predictable accessibility wins.
Tools that help: Leonardo (Adobe's open-source tool), Tailwind's default palette as a reference, and any of the perceptual colour libraries (chroma.js, culori) for programmatic ramp generation. The hard part is not the math — it is committing to a ramp and using it consistently.
Accessibility is non-optional
Every colour pairing in your palette should meet WCAG AA contrast at minimum (4.5:1 for body text, 3:1 for large text and UI elements). AAA is even better but harder to maintain across rich content. The right time to check this is in the design system, not in the QA pass on each product feature.
Practical rules we apply:
- Body text must hit 4.5:1 against any background it can land on. We typically use the 700-900 stops on a neutral ramp.
- UI affordances (icons, borders, focus rings) must hit 3:1. The 600-700 stops are usually safe.
- Disabled states are exempt from contrast minimums by spec, but we still aim for 3:1 because completely-illegible disabled states are confusing.
- Brand-coloured backgrounds must support both white and black foreground at AA. Many brands have a "primary" that fails this — the fix is to lighten or darken until the contrast clears, not to abandon accessibility.
Dark mode is a parallel system, not a filter
A common shortcut is to "dark-mode" a UI by inverting colours. The result almost always feels wrong because dark mode has different visual physics. White text on black is harsher than black text on white at the same contrast ratio because of optical halation. The whites in dark mode should usually be slightly off-white (something like #E6E6E6, not #FFFFFF), and the blacks should usually be slightly off-black (something like #0E0D0C, not #000000) for the same reason.
We design dark mode as a parallel system with its own palette. The roles are the same; the values are different. The semantic colours often need to shift hue — a green that works in light mode can look toxic in dark mode and need to be desaturated.
Cultural and category context
Colour is not universal. Red signals luck in China and danger in most of Western Europe. Green signals health in healthcare brands and money in finance. Within categories, certain palettes are saturated to the point of cliché — fintech blue, wellness sage, AI purple. Choosing a colour means deciding whether to lean into category convention (which is read as legitimate but generic) or against it (which is read as differentiated but risky).
Our default advice for new brands: lean into convention with the secondary colour, lean against it with the primary. Customers want signals that you belong in the category and signals that you are not just another player in it. Colour can do both.
How we ship a palette
The deliverable for a colour system is a tokens file (JSON or YAML) consumed by both Figma and code. The file lists every named colour, every ramp stop, every semantic mapping, and the contrast ratios for every common pairing. Designers and developers reference the same names. When the system updates, both sides update in lockstep.
This is unsexy infrastructure, but it is the difference between a palette that holds together for years and one that drifts within months. The colour theory you learned in school told you which colours look good together. The palette you actually ship has to also be legible, accessible, dark-mode-aware, and named in a way the team can remember. The wheel is the start; the system is the work.
