PalettePeek

WCAG contrast ratios explained: 4.5:1 and 3:1

7 min readUpdated July 2026
A printed color guide fanned open, showing rows of yellow, orange, pink, blue and green swatches with their codes

Every accessibility review lands on the same two numbers: 4.5:1 and 3:1. They look arbitrary until you know where they come from, what counts as “large” text, and which parts of an interface the rules cover at all. This guide walks through the formula, the thresholds, the criteria people forget, and the places where the WCAG 2.x model is genuinely wrong.

Where the number actually comes from

The ratio compares relative luminance — roughly, how much light a color sends to your eye — and not lightness as HSL reports it. For each channel you divide the 0–255 value by 255 and then linearise it: c / 12.92 when c is at most 0.04045, otherwise ((c + 0.055) / 1.055) raised to the power 2.4. That step undoes the sRGB transfer curve, which exists because displays and human vision are both non-linear.

The three linear channels are then combined as 0.2126 × R + 0.7152 × G + 0.0722 × B. Green dominates because the eye is most sensitive to it; blue barely counts. Finally the ratio is (L_lighter + 0.05) / (L_darker + 0.05), where the 0.05 models ambient light reflecting off the screen and keeps the result finite against pure black. Values run from 1:1 for identical colors to 21:1 for black on white.

Two consequences are worth keeping in mind. The formula ignores hue completely, so a saturated red and a gray of the same luminance score identically against any background. And it ignores font size and weight; those enter only as separate thresholds, never as part of the calculation.

AA, AAA and the two text sizes

Success criterion 1.4.3, Contrast (Minimum), is the level AA rule: 4.5:1 for normal text and 3:1 for large text. Criterion 1.4.6, Contrast (Enhanced), is the AAA version of the same pair, raised to 7:1 and 4.5:1. Most public-sector procurement in the EU and the US asks for AA, not AAA.

“Large” has a precise definition: at least 18 point, or at least 14 point when the text is bold. Points are not pixels — 1pt equals 1.333px at the default root size — so in CSS that is 24px regular and roughly 18.66px bold. What matters is the computed size on screen, not which tag the text sits in; an h2 styled down to 17px is normal text.

Logos and incidental text are exempt, but the exemption is narrower than teams assume. Placeholder text is not incidental — it conveys information. Text baked into a decorative photograph is exempt; the same words as a caption are not. A control that looks disabled but still responds to clicks is not exempt either.

The criterion people forget: non-text contrast

Criterion 1.4.11, Non-text Contrast, arrived in WCAG 2.1 and asks for 3:1 on the parts of a control you need to see in order to operate it, and on graphics that carry meaning. That covers the border of a text field, the outline of an unchecked checkbox, the track of a toggle, the slice colors in a chart, and any icon that has no text label next to it.

WCAG 2.2 added two more: 2.4.11 Focus Not Obscured, and 2.4.13 Focus Appearance, which asks for a focus indicator at least 2 CSS pixels thick with 3:1 contrast against the colors next to it. A 1px hairline ring in a pale gray fails both halves of that requirement, which is why a visible, chunky focus-visible style is worth designing rather than inheriting.

You do not need 3:1 between a button's fill and its label — that pair is governed by 1.4.3 — and purely decorative shapes are out of scope. When a control sits on a gradient, the measurement is taken where the control actually is, at its least favourable point.

The failures that turn up in every audit

Gray placeholder text is the most common. Browsers default to something near #757575 on white, which sits just above 4.5:1; designers routinely lighten it to #999999, which scores 2.85:1 and fails at every size. Light-gray body copy is the same mistake one step later: #8A8A8A on white is about 3.5:1 — fine for a 24px heading, not for a paragraph.

Second: white text on a mid-lightness brand color. A warm orange like #FF6B35 gives 2.6:1 with white and 2.5:1 with black — neither direction works, and no amount of arguing changes it. The usual fix is to keep the original color for large fills and derive a darker sibling from the same hue for anything set in text.

Third: text over photographs, where the ratio changes pixel by pixel. A scrim — a semi-transparent plate or gradient under the type — is the only reliable fix, and it has to be measured at the lightest point of the image. Fourth, and unrelated to contrast: links distinguished from body text by color alone fail 1.4.1 Use of Color, no matter how strong the ratio is.

Where the 2.x formula is genuinely wrong

The model is a simplification, and it is unreliable in two well-known places. Dark themes are the first: light text on a dark background often scores comfortably above 4.5:1 while looking thin and glaring, because the formula has no concept of polarity or of how light type blooms on a dark ground.

The second is the very dark end of the range. The +0.05 constant compresses differences near black, so #000000 against #222222 scores 1.68 while #FFFFFF against #DDDDDD scores 1.35 — the perceived gap is nowhere near that asymmetric. In practice this means dark-theme palettes pass more easily than they should.

APCA, the Accessible Perceptual Contrast Algorithm developed for the WCAG 3 draft, models polarity, size and weight and reports an Lc value roughly between −108 and 106 instead of a ratio. It predicts dark themes much better. It is not normative, though: if you have a legal or contractual obligation, WCAG 2.x is still what gets audited. Treat APCA as a second opinion for the cases where 2.x says “pass” and your eyes disagree.

Fixing a pair without losing the brand

Move lightness, not hue. In OKLCH you can lower L on the text color in small steps and watch the ratio climb while the color stays recognisably itself; the same edit in HSL drags the hue as it goes, which is why brand blues turn purple when they are darkened carelessly.

If the brand color must stay exactly as it is, change its job instead. Use it for headings at 24px and above, for fills, borders, charts and illustrations, and pick a darker relative from the same hue for body copy and small labels. Nobody notices that the paragraph text is three steps darker than the logo; everybody notices that they cannot read it.

Check both themes and both directions. A pair that passes as dark-on-light often fails when inverted, because the lighter color has nowhere left to go. The contrast matrix in PalettePeek exists for exactly this: it scores every combination at once, so you find the broken pairs before a reviewer does.

The takeaway

4.5:1 for body text, 3:1 for text at 24px or 18.66px bold, and 3:1 for the controls, icons and focus rings you need to see — those three numbers cover most of what an audit will ask for. Measure the pairs you actually ship, in both themes, and treat the formula as a floor rather than a verdict on how the page feels.

Related toolPalettePeek · Palette extractorDrop an image and read its colors right now