Build an accessible colour system for UI (palette to contrast)

A colour system is more than a pretty palette screenshot. It assigns roles (primary actions, body text, surfaces, borders, accents) and proves that those roles stay readable for people with low vision, in bright rooms, and on cheap laptop panels. Skipping contrast checks is how “elegant grey on white” ships and then fails real users.
This guide outlines a simple pipeline used by many product teams: generate candidates, assign roles, verify WCAG contrast, then add gradients and decoration without breaking text. ToolzWorx has free browser tools for each step so you can iterate without opening a full design suite.
Start with roles, not with twelve swatches
Decide what you need functionally: a primary brand colour, a text colour on light surfaces, a text colour on dark surfaces, a muted secondary text, success/warning/danger states, and a neutral background ladder. Extra decorative hues can wait. Systems fail when every swatch claims to be equally important.
If you already have a brand hex from a logo, treat it as a candidate primary, not automatically as body text. Saturated brand colours often fail contrast on white when used for small type. Keep the brand colour for buttons and large headings, and derive darker or softer variants for paragraphs.
Generate harmonious options
Colour theory modes give you structured starting points. Complementary pairs create strong contrast for calls to action. Analogous sets feel calm for dashboards and long reading. Triadic schemes add energy for marketing sites. Use the Color Palette Generator to explore these relationships from a base hue, then lock the keepers.
Aim for a small core: one dominant, one or two supporting hues, and neutrals for text and backgrounds. Copy hex values into a temporary list and name them by role (`primary`, `primary-hover`, `text`, `text-muted`, `surface`, `border`) so the set survives past the mood-board stage.
Verify contrast like it is a ship requirement

WCAG 2.x AA requires about 4.5:1 contrast for normal text and 3:1 for large text (roughly 18pt+ or 14pt bold). AAA raises those bars further. UI components and graphical objects generally need at least 3:1 against adjacent colours. The Contrast Checker on ToolzWorx updates live as you edit foreground and background pairs.
Test more than the hero mockup. Check body text on cards, placeholder text, link colour on tinted banners, button labels on primary fills, and disabled states (disabled can be lower emphasis, but critical information should not rely on a failed pair). If you support dark mode, retest the same roles on dark surfaces. Do not assume a light-theme pair still passes when inverted.
- Normal text: target at least 4.5:1 (AA).
- Large text: target at least 3:1 (AA).
- Icons and focus rings: aim for 3:1 against their background.
- Never rely on colour alone for errors. Pair with text or icons.
Gradients without illegible text
Gradients are fine for heroes, illustrations, and decorative frames. They are risky behind small text because contrast varies across the wash. If you must place copy on a gradient, put text on a solid scrim, restrict the gradient to areas without type, or pick stop colours that both pass against the text colour.
Build the gradient in the Gradient Builder, copy the CSS, and then sample the lightest and darkest regions under your headline colour in the Contrast Checker. If either end fails, adjust stops or add a solid overlay. Always keep a solid `background-color` fallback before the gradient rule for older clients and failed image loads.
Document the system so it stays consistent
Write the tokens down: CSS variables, a design-token file, or even a short table in your repo README. Include the hex, the role, and the contrast pair it was tested against. Future you (or a teammate) should not have to reverse-engineer why `#6B7280` exists.
When brand marketing asks for a brighter primary, re-run contrast on every text role that touches that hue. A one-line brand update can quietly break dozens of buttons if tokens are inlined as raw hex across the codebase.
Suggested workflow on ToolzWorx
Accessible colour is not the enemy of personality. It is the difference between a brand people can admire and a product people can actually read. Run the pipeline once for your core theme, then keep the Contrast Checker bookmarked for every new promotional colour that appears in a Figma file.
- Generate palette candidates in the Color Palette Generator.
- Assign roles and shortlist hex values.
- Prove text and UI pairs in the Contrast Checker (AA at minimum).
- Build decorative gradients in the Gradient Builder without placing small text on weak regions.
- Paste tokens into your project and retest dark mode surfaces.
Related tools
Use these free ToolzWorx tools alongside this guide:
More from ToolzWorx
Browse all guides, explore tools on the home page, or read About.