kuler .ai
Home / Blog / Building Color Palettes That Pass Accessibility Audits
Accessibility Color Theory

Building Color Palettes That Pass Accessibility Audits

Contrast ratios, WCAG standards, and the practical workflow for building palettes that look great and work for everyone.

· 6 min read

Accessibility isn't a constraint — it's a design skill

There's a common misconception that accessible color palettes are boring. Muted. Safe. That if you care about contrast ratios, you're giving up on vibrant, expressive design. This is wrong, and it comes from not understanding the tools.

The reality is that most contrast failures aren't caused by bold color choices — they're caused by poor pairing decisions. A vivid indigo can absolutely pass WCAG AA. The question is what you put it on top of.

The numbers you need to know

WCAG 2.1 defines three levels of contrast compliance:

  • AA Normal Text — 4.5:1 minimum ratio. This is the standard you should hit for all body copy, labels, and form text.
  • AA Large Text — 3:1 minimum. Applies to text 18px+ (or 14px+ bold). Headings often qualify.
  • AAA Normal Text — 7:1 minimum. The gold standard. Harder to achieve but significantly better for low-vision users.

These ratios are calculated from relative luminance — the perceived brightness of a color, not its raw lightness value. This is why HSL lightness is misleading for accessibility work: two colors at the same HSL lightness can have very different luminance values depending on hue.

Where OKLCH makes this easier

OKLCH's perceptual lightness (L) maps much more closely to actual luminance than HSL's lightness does. When you set two colors to the same OKLCH L value, they genuinely appear similarly bright to the eye.

This means you can reason about contrast more intuitively:

/* High contrast pair — text on background */
--bg: oklch(0.15 0.01 264);    /* very dark */
--text: oklch(0.93 0.01 264);  /* near white */
/* Lightness difference: 0.78 → easily passes AAA */

/* Medium contrast — muted text */
--muted: oklch(0.65 0.03 264);
/* Lightness difference from bg: 0.50 → passes AA */

/* Danger zone — subtle text */
--subtle: oklch(0.45 0.02 264);
/* Lightness difference: 0.30 → likely fails AA */

This isn't a precise formula — you still need to check actual contrast ratios — but OKLCH lightness gives you a much better starting intuition. If two OKLCH L values are less than 0.40 apart, check the pair carefully.

The five pairs you must check

Most palette builders check their primary text against the background and call it done. That catches maybe 30% of real-world contrast issues. Here are the pairs that actually fail in production:

  • Muted text on surface — Secondary labels, timestamps, helper text. The most commonly failed pair because both colors are "middle ground."
  • Placeholder text in inputs — Inputs often have a surface-colored background, and placeholder text is intentionally light. This combination frequently lands at 2–3:1.
  • Primary color on primary-subtle — Indigo text on a light indigo background badge. Looks fine to most eyes, often fails at 2.5:1.
  • Link text in paragraphs — Links need to be distinguishable from surrounding text. If you're using color alone (no underline), the link color needs 3:1 contrast against the text color, not just the background.
  • Icon-only buttons — No text label means the icon color itself carries all the information. Check it against its background.

Building the palette with accessibility in mind

The workflow that actually works is to design for contrast from the start, not audit it at the end. Here's the approach:

  • Step 1: Define your background and surface colors first. These are the canvases everything sits on. Get them right and the rest becomes much easier.
  • Step 2: Choose text and muted colors that clear 4.5:1 against both background and surface. Not one or the other — both. Text will appear on both.
  • Step 3: Define your brand color, then derive a "safe" variant. Your brand indigo at full chroma might not pass on all surfaces. Create a slightly darker or lighter variant that does, and use that for text and interactive elements.
  • Step 4: Check every pair, not just the obvious ones. Use a tool. Run the matrix. Fix failures before they ship.

What about dark mode?

Dark mode makes contrast both easier and harder. Easier because light text on dark backgrounds tends to have high natural contrast. Harder because surface hierarchy is more compressed — the difference between your base background and elevated surface might only be L 0.03 apart, and any text or icon needs to clear contrast against all of them.

The biggest dark mode accessibility trap is the "looks fine on my monitor" problem. OLED screens render near-black differently than LCDs. Test on both, and test at reduced brightness — that's where contrast failures become visible.

Accessible doesn't mean boring

Some of the most visually striking palettes on kuler.ai pass AAA contrast. Neon Pulse uses electric green on near-black at over 12:1. Obsidian & Gold pairs deep black with bright gold at 9:1. The constraint isn't "use less color" — it's "pair color intelligently."

Every palette on kuler.ai includes contrast ratio data for its color pairs. When you're browsing, you can see immediately whether a palette will work for your use case, no manual checking required.

Good design works for everyone. If your palette can't pass a contrast check, it's not a style choice — it's a bug.
Back to blog