Accessible colour palettes: check every pair against WCAG 2.2

How to make an accessible colour palette: WCAG 2.2 contrast ratios, why colours pass only in pairs, colour vision deficiency, APCA and Tailwind.

Summarize with
A paper sketch of a screen turning into the finished app on a phone

To make an accessible color palette, build lightness steps for each hue, then check every text colour against every background and keep only the pairs that pass WCAG 2.2: at least 4.5:1 for normal text, 3:1 for large text, and 3:1 for icons, input borders and focus rings. Publish those pairs with the palette, and give status colours a word or icon as well as a hue. A one-pair checker makes you test each combination by hand; a palette contrast checker shows all of them in one grid. Mine is free: paste your colours into the colour contrast grid as hex lines, CSS variables or a Tailwind config, and it runs in your browser.

I'm a design engineer: I design products and build them in code, and I check contrast in the tokens before a single component exists. Below is how the maths works, why no colour is accessible on its own, what APCA is (and isn't yet), and the tools I'd use besides my own.

Why a colour is only accessible in pairs

People ask whether a colour is accessible. The honest answer is: on what? Tailwind's gray-500 (#6a7282 in my tool's conversion) scores 4.83:1 on white, a pass for body text. Put the same grey on gray-100 and it drops to 4.39:1, a fail. On gray-900 it's 3.66:1: fine for a large heading, too faint for a paragraph.

So an "accessible colour palette" is really a palette with a known list of safe pairs. That list is what a design system should publish: this text token on these surfaces, this border on these backgrounds. A palette of 10 colours has 90 ordered text and background pairs, which is why checking them one at a time rarely gets finished.

The WCAG 2.2 contrast rules

WCAG 2.2 became a W3C Recommendation in October 2023, and the current version is dated 12 December 2024. Four success criteria matter for a palette:

CriterionLevelWhat it asks
1.4.3 Contrast (Minimum)AA4.5:1 for text, 3:1 for large-scale text
1.4.6 Contrast (Enhanced)AAA7:1 for text, 4.5:1 for large-scale text
1.4.11 Non-text ContrastAA3:1 for parts of controls and graphics needed to understand them
1.4.1 Use of ColorAColour can't be the only way information is shown

Large text means at least 18 point, or 14 point bold. W3C's Understanding 1.4.3 notes that 1pt is 1.333px, so that's 24px, or about 18.66px in bold. The same page says ratios are thresholds and must not be rounded: 4.499:1 does not meet 4.5:1. It also exempts logos, decoration and the text of disabled controls.

Non-text contrast covers what people need to see to use a control: an input's border, a checkbox's tick, a focus indicator, and each line or slice of a chart. Understanding 1.4.11 exempts disabled controls and charts whose values are also given as visible text. This is the rule most palettes break, because pale borders and soft brand tints look calm in a mock-up.

How the contrast ratio is calculated

The ratio is (L1 + 0.05) / (L2 + 0.05), where L1 is the relative luminance of the lighter colour and L2 of the darker one. Luminance weighs the three channels by how bright they look: 0.2126 for red, 0.7152 for green and 0.0722 for blue, after each is converted from its sRGB value to linear light. Results run from 1:1 (the same colour) to 21:1 (black on white).

The WCAG glossary notes that a threshold in that conversion changed from 0.03928 to 0.04045 in May 2021, with no practical effect. When two checkers disagree, look at rounding and the hex each one used.

How to build an accessible colour palette, step by step

  1. Collect every colour that can carry text or sit behind it. Brand colours, greys, surfaces, borders, status colours, and the dark theme's versions as their own set.
  2. Paste them into a grid. Rows are text colours, columns are backgrounds.
  3. Mark each cell by use. 4.5:1 or more: body text. 3:1 to 4.5:1: large text, icons, borders and focus rings only. Under 3:1: decoration only.
  4. Fix the pairs you need that fail. Darken or lighten one side, keep the hue.
  5. Check the palette in colour vision simulation. Pairs that differ only in hue need a second cue.
  6. Publish the passing pairs as part of the design system, so nobody has to check again.

I keep step 6 in code: a unit test runs each documented token pair through the formula, the approach in my WCAG 2.2 React checklist. If your tokens live in Figma variables and a build step, my design tokens guide covers that pipeline.

Worked example: Tailwind's default palette

Tailwind v4 defines its default colours in OKLCH, 26 named colours with steps from 50 to 950, as listed in its colours documentation. I converted a few to hex with my tool and checked them against white. Other checkers may differ by a hundredth, depending on how they round to hex.

PairRatioGood for
gray-900 text on white17.74:1Any text
gray-500 text on white4.83:1Body text
gray-400 text on white2.60:1Decoration only
White on blue-6005.25:1Button label
White on blue-5003.76:1Large text and icons only
White on green-6003.21:1Large text and icons only
green-700 text on white4.94:1Body text
White on amber-5002.14:1Fails
gray-900 on amber-5008.27:1Any text

Two patterns show up in almost every palette. Bright yellows and ambers need dark text, not white. And the 500 step of a hue is often just short of 4.5:1 for text, so status colours tend to need the 600 or 700 step to carry words.

Colour vision deficiency: why passing contrast isn't enough

According to MedlinePlus Genetics, red and green colour vision deficiency affects about 1 in 12 men and 1 in 200 women among people of Northern European ancestry, while blue and yellow deficiency affects fewer than 1 in 10,000 people. The US National Eye Institute's page on colour blindness gives the same 1 in 12 figure for men.

The contrast ratio measures lightness, so it hardly changes for these readers. What breaks is hue. Tailwind's red-600 against green-600 is 1.48:1: two colours of nearly the same lightness that many people can't tell apart. A red "failed" badge and a green "paid" badge then look the same. That's what 1.4.1 Use of Color is for: give status a word or an icon as well as a colour, and give chart lines a pattern or a direct label.

APCA: what it is and where it stands

APCA, the Accessible Perceptual Contrast Algorithm, gives a lightness contrast value (Lc) whose sign shows polarity, so dark on light scores differently from light on dark, and its README pairs each value with font sizes by weight. Its own repository says it is being evaluated as a replacement for WCAG 2 contrast maths in future standards, and that those standards are still in development.

It is not part of WCAG 2.2, and it is not yet in WCAG 3 either. The WCAG 3 Working Draft of 10 September 2026 leaves its text contrast requirement as a placeholder and says the contrast algorithm is yet to be determined. W3C's WCAG 3 introduction says WCAG 3 is a few years from completion and advises meeting WCAG 2.2 now. So: design to the WCAG 2 ratio, and treat an APCA score as extra information, never as proof of conformance.

Pitfalls that make a passing palette fail in the product

  • Transparency. A 60% black on white is a different colour from a 60% black on grey. Check what's actually rendered. My tool checks a semi-transparent text colour over its background, and a semi-transparent background over white.
  • Text on images and gradients. The ratio needs the worst part of the background, not the average.
  • States. Hover, pressed, selected and focus are new pairs. Disabled is exempt, but it should still look disabled.
  • Placeholder and helper text. It's text, so 4.5:1 applies, however quiet the design wants it.
  • Dark mode. A second palette, so a second grid. I soften pure white on near black for comfort, and the softer pair still has to pass.
  • Thin fonts. The ratio assumes normal rendering. A hairline weight at 4.6:1 can read worse than a regular weight at 4.5:1, so leave margin.

Other ways to check contrast

  • [WebAIM's contrast checker](https://webaim.org/resources/contrastchecker/). One pair at a time, with results for normal text, large text and graphical objects, a lightness slider and an alpha setting. The quickest way to settle a single pair.
  • [Chrome DevTools](https://developer.chrome.com/docs/devtools/accessibility/contrast). The colour picker shows the ratio of the element you're inspecting, draws AA and AAA lines over the shades and can apply a suggested colour; CSS Overview lists contrast issues across a page, and Lighthouse reports them. Best for a live page, which a palette grid can't see.
  • Automated audits with axe or Lighthouse in CI catch regressions in real components, including states a palette never shows.

A grid suits the palette stage; DevTools and audits suit the built product. I use both.

How my colour contrast grid works

The colour contrast grid reads a palette as you have it: hex lines, CSS variables, SCSS variables, the colours in a Tailwind config, or rgb(), hsl() and oklch() values, and takes each name from the text in front of it, so nested Tailwind names become blue-500. Then:

  • Every pair is scored with the WCAG 2 formula against 4.5:1, 3:1 for large text, 7:1 for AAA and 3:1 for interface parts. Ratios are rounded down to two places, so 4.49:1 never shows as a pass.
  • Pick a cell to see the pair as a heading, text, a button, an input and a link.
  • Fix a failing pair. It finds the nearest text colour and the nearest background that pass, keeping the hue and moving the lightness in OKLCH, lowering saturation only if the colour would leave sRGB, then checks the result again after rounding to hex.
  • Colour vision. Switch the grid to protanopia, deuteranopia, tritanopia or no colour. The ratios stay those of the real colours.
  • Export the passing pairs as CSV, Markdown or JSON for your design system docs.

Everything runs in your browser, so unreleased brand colours stay on your device. It doesn't measure APCA, and it doesn't scan a live website.

If you want the whole product designed to WCAG 2.2 AA, not only the palette, that's part of my design engineering work.

Frequently asked questions

What contrast ratio do I need for WCAG AA?

4.5:1 for normal text, 3:1 for large text (24px, or about 18.66px bold), and 3:1 for interface parts and graphics people need to see, such as input borders, icons, focus indicators and chart lines.

Can a colour be accessible on its own?

No. Contrast is always between two colours. A grey can pass on white and fail on a light grey surface, so publish the pairs, not just the colours.

Is APCA part of WCAG?

No. WCAG 2.2 uses the contrast ratio. The WCAG 3 draft of September 2026 says its contrast algorithm is yet to be determined. APCA's authors describe it as being evaluated for future standards.

How do I make an accessible colour palette?

Pick the brand hues, then build lightness steps for each, check every text and background pair in a grid, keep the steps that pass for text, and give status colours a word or icon as well. Check the dark theme as a separate palette.

How do I check my Tailwind colours for contrast?

Paste the colours from your Tailwind config, or the --color-* variables of a v4 theme, into the grid. It reads OKLCH values directly, turns nested config colours into names such as blue-500, and keeps a variable's own name, such as color-blue-500.

Does contrast help people with colour blindness?

Partly. Good lightness contrast helps everyone read, but it doesn't separate colours of the same lightness. Use labels, icons or patterns so no meaning depends on hue alone.

Sources and method

I read every source on 1 November 2026. The Tailwind ratios come from converting Tailwind's OKLCH values to hex with my own tool and applying the WCAG 2 formula; I state the hex I used for gray-500 so you can recheck it.

  • W3C, Web Content Accessibility Guidelines (WCAG) 2.2, Recommendation of 12 December 2024, including the relative luminance and contrast ratio definitions, date read 1 November 2026: w3.org
  • W3C, Understanding Success Criterion 1.4.3 Contrast (Minimum), date read 1 November 2026: w3.org
  • W3C, Understanding Success Criterion 1.4.11 Non-text Contrast, date read 1 November 2026: w3.org
  • W3C, WCAG 3.0 Working Draft of 10 September 2026, date read 1 November 2026: w3.org
  • W3C WAI, WCAG 3 Introduction, date read 1 November 2026: w3.org
  • APCA, apca-w3 repository README, date read 1 November 2026: github.com
  • MedlinePlus Genetics, Color vision deficiency, date read 1 November 2026: medlineplus.gov
  • National Eye Institute, Color Blindness, date read 1 November 2026: nei.nih.gov
  • Tailwind CSS, Colors documentation, date read 1 November 2026: tailwindcss.com
  • WebAIM, Contrast Checker, date read 1 November 2026: webaim.org
  • Chrome for Developers, Make your website more readable (contrast in DevTools), date read 1 November 2026: developer.chrome.com

If you're building a product and want the palette, the components and the code checked against WCAG 2.2 AA from the first screen, book a free 30 minute intro call. After it, you get a written scope and one fixed quote.

Tags: Accessibility, Colour, WCAG, Design systems, Free tools

Keep reading

Let’s look at your product.

A free 30 minute call. You leave with a clear next step, whether we work together or not.

Book a free intro call