Skip to content
Back to list

Contrast is a ratio, not a feeling

A color pair can look sharp on your monitor and hard to read on a phone in sunlight. Contrast is not a guess. It is a measured gap between two colors, and the web already has rules for how large that gap must be.

Remember these three numbers:

  • 4.5:1 for normal text at WCAG AA
  • 7:1 for normal text at WCAG AAA
  • 3:1 for large text at AA, and for UI parts that are not text

The rest of contrast work is choosing which number you need, and which pairs in the UI actually matter.

What the ratio measures

WCAG measures contrast from brightness (relative luminance), not from how saturated or "strong" a color feels. Two bright colors can still fail. Two gray colors can pass.

The formula is:

(L1 + 0.05) / (L2 + 0.05)

L1 is the lighter color. L2 is the darker color. The 0.05 keeps the math stable when one color is near black.

You do not need to do this by hand. You do need a real number. "It looks fine in Figma" is not a test.

AA is the baseline. AAA is a choice.

Level AA is the usual target for public products in 2026. When teams say "accessible contrast," they usually mean AA.

ElementAAAAA
Normal text4.5:17:1
Large text3:14.5:1
UI components and important graphics3:1

AAA is not "extra credit." It is a separate decision. Body text, errors, and key labels are worth 7:1. A brand color on a button often cannot hit 7:1 without becoming a new color. WCAG says AAA is not realistic for a whole interface. Use AAA where people read. Use AA everywhere else.

"Large text" is smaller than it sounds

This is the most common mix-up.

In WCAG, large text is 18pt regular or 14pt bold. In CSS that is about 24px regular or 18.5px bold. A 20px heading at regular weight is still normal text. It needs 4.5:1.

If your section titles are 18px or 20px, treat them as normal text. Only true display sizes get the 3:1 rule, and only when they are large enough and heavy enough.

Text is not the only thing that needs contrast

WCAG 1.4.11 also asks for 3:1 on UI parts people need in order to use the screen. That includes:

  • Input borders against the field
  • Focus rings against the background
  • Icons that mean something (delete, warning, success)
  • Chart pieces that must be told apart
  • The edge of a selected chip or tab

Placeholder text is text. Helper text is text. A disabled label that still shows a state is text. "It is only a hint" is not an exception.

Logos and text that just happen to sit inside a photo are exempt. Marketing headlines saved as images are not.

Check these pairs first

Do not start with the hero title on white. Start here:

  1. Body text on the real page background. If the page is #F7F7F8, do not test against #FFFFFF.
  2. Muted text. #9CA3AF on white is about 2.8:1. It fails. Teams still ship it because it "looks secondary." Secondary is not an accessibility level.
  3. Text on tinted surfaces — selected rows, info banners, colored cards.
  4. Button label on the button fill, including hover and disabled. If the control is visible, the label still needs to be readable.
  5. Placeholder text and input borders.
  6. Icons and focus rings on both light and dark surfaces.

A two-color checker is the right tool for each pair. It is the wrong tool for "is this whole palette safe?" — that is a question for a named 10-step scale, checked pair by pair once it exists. Start with the pairs people actually read.

Color alone is not enough

Good contrast does not fix a status message if success is only green and error is only red. About 8% of men have some form of color vision deficiency. Add a label, an icon, or a pattern. Contrast makes the text readable. Extra signals make the meaning clear.

A simple rule for a design system

  • Body, labels, and primary actions: 7:1 when the brand allows it, 4.5:1 at minimum.
  • Real large headings: 4.5:1 if you can, 3:1 as the floor.
  • Borders, icons, and focus: 3:1 against the color next to them, not against white from another frame.
  • Do not ship a text-muted token that fails 4.5:1 on the background in your docs.

When a pair fails, do not only lighten the type. Darken the type, darken the surface, or move to a nearby step of the same hue that passes. Keep the hue. Change the brightness.

Check the pair. Then ship the pair.

Pick two colors. Read the ratio. Read the AA and AAA results for normal text, large text, and UI. If it fails, change a color before you change the copy.

That is the job. The contrast checker does it in the browser, on the real hex values, with no account and no upload.

Tools mentioned