Building an Accessible Color Palette From Scratch
A field guide to picking 5-12 colors that pass WCAG, work for color-blind users, and still look like a brand - not a compliance report.
"Accessible" is not a separate palette. It's a constraint that, if you respect it from the start, makes every other palette decision easier. Here's a step-by-step that ends with a five-to-twelve-color set you can ship.
Step 1: Pick the brand color last
Start from the neutrals. Decide your background and your darkest text first - these two colors carry 90% of every screen and have to pass 4.5:1 contrast cleanly. A pure-white-on-pure-black is 21:1 (plenty); off-white on off-black is around 18:1; very-light-gray on very-dark-gray drops to 12:1 - still fine.
Step 2: Build a five-step neutral ramp
Generate five neutral steps in OKLCH between your darkest text and your background. Use these as: text-primary, text-secondary, border, surface-raised, surface. Equal-feeling steps make the whole UI feel calm.
Step 3: Add the brand color
Now drop in the brand. Pick a hue that is not too close to a critical state color (red, green, yellow). Tune its lightness so it sits at 4.5:1 against your background AND against your text. If it can't, generate a slightly-darker variant for use on text.
Step 4: Add the semantic four
Most products need: success, warning, danger, info. Pick hues that are visibly different in lightness, not just hue - this is what saves you for color-blind users. A good test: convert all four to grayscale; if you can still tell them apart, you're safe.
Step 5: Run the deuteranopia test
Drop the full palette into our Colorblind Simulator. Pay extra attention to deuteranopia (the most common form, ~6% of men). If your success-green and warning-amber become the same color, change one's lightness until they don't.
Step 6: Audit every text-on-surface pair
Run the matrix in Contrast Checker (Matrix tab). Every text token on every surface token should hit 4.5:1 (body) or 3:1 (large). The matrix shows you which pair fails before a user does.
Step 7: Mirror for dark mode
Reverse the neutrals; gently desaturate the brand and semantic colors (saturated hues feel more aggressive on dark backgrounds). Re-run the contrast matrix. Done.
Quick checklist
- Body text - 4.5:1 against every surface.
- Large text and icons - 3:1.
- Status colors distinguishable in grayscale.
- Brand color works at body-text size.
- Focus ring color hits 3:1 against any adjacent color.
Hit all five and you have an accessible palette. Skip the brand-first instinct; let constraints lead. The result almost always looks more confident than a "fun" palette retrofit later.