How to Build an Accessible Colour Palette
You built a palette you love. The greens sing, the accent pops, the whole thing feels like you. Then someone squints at your buttons and can't read a word — and you realise "beautiful" and "readable" were never the same test.
Accessibility gets treated like a chore you bolt on at the end. A checkbox. It isn't. Done right, it's part of how you pick colour in the first place, and it makes the palette better, not blander. Here's the workflow I run, in order, every time.
Beauty and readability are one job, not two
The trap is thinking you design first and audit later. You pick five gorgeous swatches, ship them, and only then discover the light-grey caption is a ghost. Now you're renegotiating the whole thing under pressure.
Flip it. Bake the checks into the choosing. Every time you commit a colour to a role — text, background, button, link — you ask one question on the spot: can a real human read this against what sits behind it? The palette that survives that question isn't uglier. It's just honest about where the light falls.
Check every text-on-background pair
Contrast is the non-negotiable one. Two colours that look fine side by side as swatches can turn to mush the second one becomes 14px body text on the other.
The bar is a number. WCAG asks for a contrast ratio of at least 4.5:1 for normal text, 3:1 for large or bold text. You don't eyeball this. Run each pair — dark text on your background, white text on your button, your link colour on the card it lives in — through the Contrast Checker. It gives you the ratio and, when you miss, hands you a tuned shade that clears the bar without throwing away the hue.
One thing people botch: they test the main pairs and forget the edges. Placeholder text. Disabled states. A caption at #9aa0a6 on white looks tasteful and scores a lazy 2.6:1. Test the boring pairs too. That's where readability quietly dies.
Run the whole palette through colour-blind eyes
Somewhere between five and eight percent of men can't reliably tell your red from your green. That's not an edge case. That's one in twelve of the people looking at your work.
So a palette isn't accessible just because each pair passes contrast. It also has to survive being seen differently. The classic failure: a green "success" and a red "error" that turn into two identical muddy browns for a big slice of your audience. Same lightness, different hue — and hue is exactly the channel that drops out.
Two habits fix most of it. Spread your key colours across lightness, not just hue, so they stay distinct even when the colour information collapses. And drop your palette onto real components in the UI Preview — buttons, badges, cards — then imagine the hues muted toward grey. If two roles blur together, they were leaning on colour alone.
Never let colour be the only signal
This is the rule that separates a pretty palette from an accessible one. Colour can carry meaning. It can't be the only thing carrying it.
Think about the last form you filled in. A field turned red. Did anything else tell you it failed? If not, a colour-blind user got no message at all. The fix costs almost nothing:
- Pair the red error state with an icon and the words "Required field."
- Give the active nav item an underline or a left border, not just a brighter tint.
- In a chart, label the lines directly or vary their dash pattern instead of trusting the legend swatch.
Redundancy is the whole idea. Colour plus shape, colour plus text, colour plus position. When you strip the colour out entirely and the interface still makes sense, you've done it right.
Lock the accessible choices as tokens
Here's what usually happens next. You do all this work, you get the palette clean — and three sprints later a teammate grabs a hex straight off a mockup and quietly reintroduces the ghost caption. All that care, gone, because the good decision lived in your head instead of the codebase.
Name the roles and freeze them. Not blue-500 — that's just a colour. text-primary, surface, border-subtle, action. Roles that describe the job, each mapped to a value you already checked. Do it in the Design Tokens tool and export the set, so the contrast-safe pairing is the default a developer reaches for, not a thing they have to remember.
If your tokens need to land in oklch() for a modern system, or plain rgb() for an older one, push the values through the Converter first. One source of truth, expressed in whatever the code wants.
Common questions
Does designing for accessibility make my palette boring?
No, and this myth needs to die. The constraints touch contrast and redundancy — not your hue choices. You can build something loud, saturated and strange and still clear 4.5:1. Accessibility limits the pairings you use, not the colours you're allowed to love.
What's the one check to run if I only have five minutes?
Contrast on your body text against its background. It's the single most common failure and the one that shuts the most people out. Everything else is a close second.
Do I really need tokens for a tiny project?
For a one-off landing page, no. The moment more than one person touches the code, or the site outlives the month, yes — tokens are how the accessible choice stays the default instead of decaying.
Accessible colour isn't a filter you apply at the end. It's four small habits — check the pairs, test for colour blindness, add a second signal, lock the roles — woven into how you pick. Start with the pairs you already have and run them through the Contrast Checker. Fix the ghost caption today.
Ready to try it? Every tool on Paleta runs free in your browser — no sign-up, nothing uploaded.
Explore the tools →