Building an Accessible Color Palette From Scratch

"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.