CODEXA.Technologiez
All insightsDesign

Building a colour system that passes WCAG

Codexa Design · Sep 18, 2026 · 3 min read

A palette gets signed off as swatches on a white board, then meets a form field, a disabled state and a piece of small print, and stops working.

The fix is not fewer colours. It is deciding what each colour is *for* before deciding what it looks like.

Thresholds, and what they apply to

  • Normal body text: 4.5:1 for WCAG AA.
  • Large text — 18.66px bold or 24px regular and above: 3:1.
  • Interface components, borders, icons and focus rings: 3:1.
  • AAA raises the first two to 7:1 and 4.5:1.

The one most often missed is the third. Teams check their body text and ship a form with a border nobody with low vision can locate.

Mid-tones are where palettes fail

The common failure is a brand colour in the middle of the lightness range. It is too dark for white text and too light for black, so it fails in both directions.

The answer is almost never to abandon the colour. Keep it for large display type, backgrounds and accents, and derive a darker variant for anything small. One brand colour becomes a small family with defined roles.

Give every token a job

Instead of "burgundy", "gold", "grey", name what each is for: page background, card surface, body text, muted text, border, brand, accent.

Roles make contrast testable — you know which pairs will actually occur — and they make dark mode possible, because you redefine the roles rather than reinventing the palette.

Dark mode is not inversion

Flipping lightness produces pure black backgrounds and over-saturated brand colours that vibrate against them.

Reflect lightness around the midpoint and compress slightly toward the middle, so near-white surfaces become near-black rather than absolute black, and reduce saturation a little — a colour that reads as confident on white reads as loud on black.

Test the pairs, not the swatches

A palette is not accessible; a *pair* is. Six colours make thirty possible combinations and you will use perhaps eight of them.

Our contrast checker grades every pair in a palette at once as a grid, which makes the failing combinations obvious before they reach a screen. If you are working in Tailwind v4, our theme generator emits the tokens with light and dark variants and flags failing pairs as you go.

Colour is never the only signal

Around one in twelve men has some form of colour vision deficiency. Any state communicated by colour alone — a red invalid field, a green success message, a status dot — is invisible to some portion of your users.

Pair colour with an icon, a label or a shape. It costs nothing and it is the difference between a state being decorative and being information.

What contrast ratio do I need for WCAG AA?

4.5:1 for normal body text and 3:1 for large text, where large means 18.66px bold or 24px regular and above. Interface components — borders, icons, focus rings, form outlines — need 3:1 against whatever sits behind them. That last one is the threshold most teams miss, shipping readable body text alongside a form border nobody with low vision can locate.

Why does my brand colour keep failing contrast?

Mid-tone colours are the usual culprit: too dark for white text and too light for black, so they fail in both directions. The fix is rarely to abandon the colour. Keep it for large display type, backgrounds and accents, and derive a darker or lighter variant for anything small — one brand colour becomes a small family with defined roles.

Enjoyed this? Let's work together.

Start a project