Command Palette

Search for a command to run...

Color Contrast Checker: How to Hit WCAG AA and AAA Without Guessing

Color Contrast Checker: How to Hit WCAG AA and AAA Without Guessing

T
Toolz Team
|Jul 22, 2026|16 min read

The first accessibility bug I ever shipped was a light grey timestamp on a white card. It looked elegant in Figma, it looked fine on my calibrated monitor, and it was completely invisible to a client reading email on his phone in a parked car. He wasn't visually impaired. It was just sunlight. That single support ticket taught me something the design mockup never could: contrast is not a matter of taste, it is a measurable property, and if you don't measure it you are shipping guesses.

Since then I have built the Color Contrast Checker on toolz.dev, wired contrast rules into the design tokens for WP Adminify, and audited more React components than I care to count. This guide is the version of that experience I wish someone had handed me at the start. It explains what the contrast ratio actually is, how the number is calculated, what the WCAG thresholds mean, and how to fix a failing color pair without throwing away your brand palette.

TL;DR: WCAG contrast is a ratio from 1:1 to 21:1 based on the relative luminance of two colors. For normal body text you need at least 4.5:1 to pass AA and 7:1 to pass AAA. Large text (about 24px, or 18.66px bold) drops to 3:1 for AA and 4.5:1 for AAA. Non-text UI like icons and input borders needs 3:1. Measure every text-on-background pair, treat AA as the pass bar, and let the tool suggest a passing color when you fall short.

Why does color contrast matter so much?

Contrast is the single most common accessibility failure on the web. Year after year, automated audits of the top million home pages find that low-contrast text is the most frequent WCAG violation by a wide margin. It is also the one that affects the most people, because it does not only concern users who are registered blind. It concerns anyone with reduced vision from age, anyone with a common condition like cataracts, anyone using a cheap display, anyone in a bright room, and every single one of us when the sun hits the screen.

There is a business angle too, and I am not going to pretend otherwise. Accessibility law now references WCAG directly in many regions, which means an inaccessible interface is a legal exposure, not just a courtesy problem. But the reason I care, and the reason I built a dedicated tool for it, is simpler. Text that people cannot read is text that failed at its only job. A gorgeous interface nobody can use is not a design achievement.

The trap is that our eyes are unreliable narrators. We adapt to our own screens, we know what the text is supposed to say so our brain fills it in, and we look at designs in ideal conditions. The contrast ratio removes all of that. It gives you a number that does not care about your monitor or your expectations.

What exactly is a contrast ratio?

The contrast ratio is a comparison of the relative luminance of two colors: your foreground (usually text) and your background. Relative luminance is a measure of how much light a color emits or reflects, weighted for how the human eye actually perceives red, green, and blue. Green contributes the most to perceived brightness, blue the least, which is why pure blue text on black is so much harder to read than pure green.

The ratio itself is defined by WCAG as:

(L1 + 0.05) / (L2 + 0.05)

where L1 is the relative luminance of the lighter color and L2 is that of the darker one. The 0.05 terms model ambient light reflecting off the screen, which is why the scale never quite reaches infinity. Identical colors produce a ratio of exactly 1:1. Pure black text on pure white gives the maximum of 21:1. Everything real lives between those two extremes.

The important thing to internalise is that the ratio is not linear with how "different" two colors look in the hue sense. Two colors can be wildly different in hue β€” say a saturated red and a saturated green β€” and still have almost the same luminance, which makes them a nightmare for readability even though they clash violently. Contrast is about lightness difference, not color difference. That is exactly why you cannot eyeball it, and why the Color Contrast Checker converts both colors to luminance before doing any comparison.

How is the number actually calculated?

If you want to understand the tool rather than just trust it, here is the full pipeline, which follows the WCAG 2.2 definitions exactly.

First, each color channel is normalised from the 0–255 range to 0–1. Then each channel is linearised to undo the sRGB gamma curve. The formula applies a small linear segment for very dark values and a power curve for the rest:

c_linear = c / 12.92                        if c <= 0.03928
c_linear = ((c + 0.055) / 1.055) ^ 2.4      otherwise

Next, the three linearised channels are combined with the luminance coefficients that reflect human perception:

L = 0.2126 * R + 0.7152 * G + 0.0722 * B

Those weights are not arbitrary. They come from the way the eye's cone cells respond to different wavelengths, and they are why green dominates. Finally, the two luminance values are plugged into the ratio formula above, with the lighter one on top.

There is one subtlety that trips people up, and it is transparency. A contrast ratio is only meaningful between two opaque colors, because a semi-transparent color's real appearance depends on whatever sits behind it. If your text color has an alpha value, you cannot measure it directly. The correct approach, and the one the tool takes, is to composite the translucent foreground over the background first using standard source-over blending, then measure the ratio against the flattened result. This matches what a user actually sees rather than the color you nominally set.

What are the WCAG thresholds I need to hit?

WCAG defines contrast requirements at two conformance levels and for three broad content types. Here is the whole picture in one place.

Content type AA minimum AAA minimum
Normal text (under ~24px) 4.5:1 7:1
Large text (~24px, or ~18.66px bold) 3:1 4.5:1
UI components and graphics 3:1 3:1 (no separate AAA)
Logotypes and incidental text Exempt Exempt

A few notes from having applied this table many times. "Large text" is defined by rendered size, not by how important the text feels. The threshold is 18pt for regular weight and 14pt bold, which translate to roughly 24px and 18.66px. Anything at or above that gets the relaxed 3:1 bar for AA, because larger letterforms remain legible at lower contrast.

The UI-components rule is Success Criterion 1.4.11, and it is the one teams forget most often. It says the visual boundary of an interactive component β€” the border of a text input, the outline of a button, the shape of an icon that conveys meaning β€” needs 3:1 against its surroundings. A form full of AA-passing label text with invisible input borders still fails, and I have seen exactly that in production more than once.

Finally, decorative text, disabled controls, and logos are exempt. That exemption is narrow. A greyed-out button label is fine; a low-contrast paragraph you called "secondary" is not.

How do I use the Color Contrast Checker?

The workflow I use daily takes about ten seconds per pair. Open the Color Contrast Checker and enter your text color in the first field. You can paste any format β€” a six-digit hex like #4b5563, an eight-digit hex with alpha, an rgb() or rgba() string, an hsl() value, or a plain CSS keyword like navy. The tool normalises whatever you give it, so you can mix formats freely between the two fields.

Enter your background color in the second field. The moment both are valid, the exact ratio appears alongside a live sample of large, normal, and caption text rendered on your actual background. That preview matters. The number tells you whether it passes, but seeing real words on the real color tells you whether it is comfortable, which is a higher bar than merely legal.

Below the ratio you get a grid of pass and fail badges: AA and AAA for both normal and large text, plus the 3:1 UI-component check. If a combination fails, the tool offers a one-click fix. It walks your text color lighter or darker in HSL space β€” preserving the hue and saturation you chose β€” until it clears the target ratio, then hands you a passing value you can copy straight into your stylesheet. That last part is the difference between a checker that scolds you and one that actually helps: it keeps your brand hue intact and only moves the lightness by the minimum needed.

Everything runs locally in your browser. No colors, palettes, or unreleased brand values leave your device, which matters when you are auditing a client's pre-launch design system. It is the same privacy-first approach I use across every tool on the site, and I wrote about why that architecture matters in the data privacy guide.

Common use cases from real projects

Auditing a design system's tokens

When I set up color tokens for a new project, I check every semantic pairing before a single component ships: primary text on background, muted text on background, text on primary, text on card, text on every alert variant. A token that fails once fails everywhere it is used, so catching it at the token level is enormously cheaper than catching it per component. I keep a simple checklist and run each pair through the checker, recording the ratio next to the token name.

Rescuing a brand color that "has to" be used

Marketing hands you a brand blue and says the buttons must use it. You test white text on it and get 3.1:1 β€” fine for large text, failing for a normal-size button label. You have three honest options: darken the brand blue slightly for interactive surfaces, use a larger or bolder label so the 3:1 threshold applies, or switch the label to a darker color if the button is light. The tool's suggestion feature finds the nearest passing shade so you can show marketing a concrete, minimal change rather than a flat "no."

Checking placeholder and helper text

Placeholder text is a repeat offender because browsers render it at reduced opacity by default, and designers then pick an already-light grey on top of that. Remember to test the composited result, not the nominal color. The checker's alpha compositing does this for you: enter the placeholder color with its real opacity over the input background and read the true ratio.

Verifying icon and border visibility

For the 1.4.11 UI check, I test the icon or border color against whatever sits immediately behind it. A grey divider on a white card, a chevron on a coloured header, the focus ring against the field β€” all of these need 3:1. It is a quick check that catches the "everything is technically there but I can't see any of it" class of bug.

How does this compare to just using a design tool's built-in check?

Design tools have added contrast indicators, and browser dev tools show a ratio in the color picker, so a fair question is why reach for a dedicated tool at all. Here is my honest comparison.

Approach Strength Limitation
Dedicated contrast checker Exact ratio, all thresholds, alpha compositing, passing-color suggestions, works on any color string You paste colors in manually
Design tool plugin Inline with your artboard Often approximates, may ignore alpha, tied to that app
Browser dev tools Checks the live rendered page One pair at a time, no suggestions, requires the page to exist first
Eyeballing it Fast Wrong, constantly

The dedicated tool wins whenever you are working from values rather than a rendered page β€” reviewing tokens, checking a spec, or fixing a reported bug β€” and whenever you need a passing alternative rather than just a verdict. The browser dev tools are better once the page exists and you want to spot-check what actually shipped. I use both, at different stages.

Advanced notes for teams that care

A few things I have learned that go beyond the basics. First, contrast is necessary but not sufficient. Passing 4.5:1 removes one barrier; it does not make an interface accessible on its own. You still need visible focus states, you still must avoid conveying meaning through color alone, and you still need real testing with real assistive technology. Treat the ratio as a floor, not a finish line.

Second, dark mode is not free. A palette that passes in light mode can fail in dark mode because the luminance relationships invert. Check both themes. The checker's dark-background preview toggle exists precisely because I kept forgetting to.

Third, be wary of "close enough." A ratio of 4.48:1 is a fail, not a rounding win. Screens vary, users vary, and the threshold has margin built in for a reason. If you are borderline, push past it.

If you are assembling a broader toolkit, the web developer toolkit guide covers how contrast fits alongside the other checks worth automating. And once your colors pass, tools like the Gradient Generator and Color Picker help you build the rest of the palette on a foundation you have already verified. If you are also generating page metadata and social previews, the Meta Tag Generator pairs well in the same pre-launch pass.

FAQ

What is a good color contrast ratio?

For normal body text, aim for at least 4.5:1 to meet WCAG AA and 7:1 to meet the stricter AAA level. Large text, meaning about 24px or 18.66px bold, only needs 3:1 for AA and 4.5:1 for AAA. Icons and interactive UI components need at least 3:1 against their surroundings.

What counts as large text under WCAG?

Large text is at least 18pt, roughly 24px, for regular weight, or at least 14pt, roughly 18.66px, when bold. Text at or above that size qualifies for the relaxed 3:1 AA threshold because bigger letterforms stay legible at lower contrast than small ones.

What is the difference between WCAG AA and AAA?

AA is the level most laws and organisations target and requires 4.5:1 for normal text. AAA is the enhanced level and requires 7:1, which suits content aimed at users with low vision but is difficult to hit across an entire palette. Most teams treat AA as the pass bar and AAA as an aspiration for critical reading content.

How is the contrast ratio calculated?

Each color is converted to its WCAG relative luminance, a gamma-corrected and perceptually weighted measure of brightness. The ratio is (L_lighter + 0.05) divided by (L_darker + 0.05), producing a value from 1:1 for identical colors to 21:1 for pure black on pure white.

Does transparency affect the contrast result?

Yes. A semi-transparent text color lets the background show through, which changes its effective color. The correct method is to composite the translucent foreground over the background first, then measure the ratio, so the score reflects what a user actually sees rather than the nominal color you set.

Why does my text pass at large size but fail at normal size?

The thresholds differ. Normal text needs 4.5:1 for AA while large text only needs 3:1, so a ratio between 3 and 4.5 passes for headings but fails for body copy. Either raise the contrast for body text or make sure that pairing is only used at large sizes where the relaxed threshold applies.

Is a passing contrast ratio enough for accessibility?

No. Contrast is necessary but not sufficient. You also need visible focus states, you must avoid conveying meaning by color alone, and you should test with real content and assistive technology. Passing the ratio removes one common barrier but does not by itself make an interface accessible.

Does the color contrast checker send my colors anywhere?

No. All parsing and math run locally in your browser using JavaScript. No color values, brand palettes, or screenshots are uploaded or stored, and the tool continues to work with no network connection once the page has loaded.

Comments

0 comments

0/2000 characters

No comments yet. Be the first to share your thoughts!