Accessible colour palettes that don't look boring

There’s a myth that accessible design means safe, muted, black-on-white — that the moment you care about contrast, your palette gets boring. It doesn’t. Some of the boldest, most characterful sites on the web pass WCAG comfortably. The trick is knowing which role each colour plays, because contrast rules only apply to certain roles.

The key insight: contrast is about pairs, not colours

WCAG doesn’t say “this colour is inaccessible.” It only ever talks about the contrast between two colours — specifically, between text (or a UI element) and its background. That means a vivid, saturated colour isn’t a problem in itself. It’s only a problem if you put small text on it, or it on small text.

So a colour can be as loud as you like when it’s used as:

  • a large background block,
  • a big shape or hero fill,
  • a decorative element,
  • a button background (with carefully chosen text on top).

It only has to behave when it’s carrying small text or acting as a UI outline.

A repeatable method

Here’s a way to build a palette that keeps its personality and passes:

1. Pick your brand colours freely

Choose the 2–3 colours that make your brand yours. Don’t compromise them yet. These are your “identity” colours — they’ll mostly live in large areas.

2. Derive a dark and a light “text-safe” version of each

For each brand colour, create:

  • a dark variant (drop the lightness a lot) that clears 4.5:1 on white or on light backgrounds, and
  • a light/tint variant for backgrounds behind dark text.

Crucially, keep the hue and saturation — you’re only moving lightness. Your brand blue’s dark variant still reads as your brand blue; it’s just usable as text. The contrast checker’s “Fix it for me” button does exactly this move automatically: hold the hue, walk the lightness to the nearest passing value.

3. Assign roles

Now map colours to jobs:

  • Big areas & heroes: your loud, saturated brand colours. No contrast constraint (unless text sits on them).
  • Body text: a near-black or a dark brand variant at 7:1 if you can — comfortable to read.
  • Buttons: brand colour as the fill, with white or near-black text chosen to clear 4.5:1 on that fill.
  • Accents, borders, icons: must clear 3:1 against their background (that’s the non-text rule).

4. Test every text/background pair you’ll actually use

This is the step people skip. You don’t need every colour to contrast with every other colour — only the combinations you’ll actually ship. Text on white, text on your brand fill, white on your button, accent on your card background. Run each real pair through the checker.

Worked example

Say your brand colour is a bright teal, #12B5A5. Gorgeous as a hero background, but as text on white it’s around 2.6:1 — a clear fail.

  • Hero / big blocks: use #12B5A5 at full strength. Fine.
  • Teal text on white: darken to about #0A6E63 — same hue, now roughly 6:1. Passes AA comfortably.
  • Button: #12B5A5 fill with #FFFFFF text? About 2.6:1 — fails. Use near-black text (#0F2E2A) on the teal instead, which clears 4.5:1, or darken the fill.
  • Accent border on a white card: #12B5A5 at 2.6:1 fails the 3:1 rule — darken it to #0E9082 (nearly 4:1) for outlines.

Same personality, same teal identity, fully accessible — because each use of the colour was assigned to a role and checked.

The takeaway

Accessible ≠ boring. Loud colours are welcome; they just belong in large areas, while text and UI elements use lightness-adjusted variants of the same hues. Pick boldly, derive text-safe versions, assign roles, and test the real pairs. Do that and you get a palette with contrast and character.

Start deriving your text-safe variants in the contrast checker — pick your brand colour, and let “Fix it for me” find the nearest passing shade.

Open the contrast checker →