The Complete Guide to Color Contrast
Color contrast is the difference in brightness between text and its background, and it’s the most common accessibility failure on the web today. A 2025 WebAIM scan found low-contrast text on 79.1 percent of scanned homepages, which means most sites are shipping text some visitors genuinely cannot read. That’s not a hypothetical edge case, it’s the majority default. The fix isn’t complicated. It’s a math problem with a clear pass-fail answer, and getting it right protects both your visitors and your legal exposure.
Key takeaways
- Normal text needs at least 4.5:1 contrast against its background to meet WCAG AA. Large text needs 3:1.
- UI components like input borders, icons, and focus indicators need 3:1 too, not just text.
- Light brand colors, yellows, pastels, light oranges, almost always fail AA as body text.
- Build a lightness ramp of a brand color in OKLCH instead of abandoning a color that fails contrast.
- Accessibility-overlay widgets do not provide legal cover and have not held up in court.
What color contrast actually measures
Contrast ratio measures the difference in relative luminance, essentially perceived brightness, between two colors, expressed as a ratio from 1:1 (identical, no contrast) up to 21:1 (pure black on pure white, the maximum possible). It isn’t about whether two colors “feel” different to look at. It’s a calculation based on how light or dark each color actually is, which is why a pairing can look fine to one person’s eyes and still fail the math.
This matters because a meaningful share of visitors to any site have some degree of low vision or color blindness, or are simply reading a phone screen in bright sunlight. Good contrast isn’t a nice-to-have layered on top of the design. It’s what makes the design work for the people actually using it.
The WCAG ratios you actually need to hit
- Normal text, AA: at least 4.5:1 against its background.
- Large text, AA (18pt+/24px+, or 14pt+/18.66px+ bold), at least 3:1.
- UI components and graphical objects, AA: at least 3:1 against adjacent colors, icons, input borders, focus indicators.
- Normal text, AAA: at least 7:1, a stricter standard worth targeting for healthcare, government-adjacent, or accessibility-focused audiences.
AA is the default target for most projects. Treat AAA as a stretch goal rather than the floor, unless the client’s sector or audience specifically calls for the higher standard.
A concrete example: pure black text on white hits the maximum 21:1. A mid-gray around #767676 on white lands right around 4.54:1, just barely clearing AA for normal text, which is why that exact shade shows up so often in accessible design systems as the default “muted” text color. Push even slightly lighter than that and it drops below the line.
How to check contrast, don't eyeball it
Eyeballing contrast is unreliable even for trained designers, because perceived brightness doesn’t map cleanly to intuition, especially across different hues. Use a real contrast checker instead, WebAIM’s Contrast Checker for a quick manual spot-check, or an automated scanner like axe or Lighthouse to catch every text and background pairing across an entire site at once. Automated tools catch a meaningful share of accessibility issues fast and cheap, roughly a third to half of real issues by most estimates, so pair an automated scan with a manual check on anything the scanner flags as borderline.
Check every pairing that will actually ship, not just the obvious ones. Body text on the main background is the easy case. Button text on a hover state, placeholder text in a form field, and small print in a footer are the pairings that get missed.
Run the check at the actual point of use, not just on a swatch in isolation. The same accent color might pass comfortably as large heading text on a light background and fail outright as small body text on a slightly different shade of the same background a few sections down the page. Contrast is a pairing, not a property of one color alone, and it has to be verified pairing by pairing.
The most common contrast traps
Light brand colors are the most frequent offender. Yellows, light oranges, and pastel accent colors almost always fail AA against a white background, no matter how good they look as a logo mark or a graphic accent. If a client’s preferred brand color is a light or bright hue, flag it early and design the most saturated version for backgrounds and large graphic elements rather than body text.
Gray-on-white is the second trap, a design instinct toward “softer” text that quietly drifts below 4.5:1 without anyone noticing until an audit catches it. Placeholder text and disabled states are the third: both get styled light almost by convention, and both routinely fail contrast even though they still carry meaning a user needs to read.
A specific number worth remembering: many popular light yellows sit well under 2:1 against white, nowhere close to the 4.5:1 floor, even though they photograph well in a brand mood board. The fix isn’t switching away from yellow entirely. It’s reserving the bright version for large graphic elements and backgrounds, and using a darker, more muted step of the same hue anywhere it needs to carry text.
Building an accessible palette from one brand color
Most clients arrive with a brand color already chosen, sometimes one that doesn’t clear contrast on its own. The fix isn’t to abandon the color. It’s to build a lightness ramp of that same hue, the same color at systematically varied lightness and saturation steps, and pick the step that clears AA against the specific background it needs to sit on.
Building the ramp in OKLCH rather than standard HSL or hex values makes this predictable instead of a guessing game. OKLCH is a perceptually uniform color space, meaning two colors at the same lightness value actually look equally bright to a human eye across every hue, which isn’t true in older color models. That property makes it possible to generate an accessible palette by construction, holding lightness steady and letting contrast stay consistent, instead of hand-tuning every shade one at a time, the same token discipline a full brand identity system relies on.
In practice, this usually means generating five to seven lightness steps of the brand hue, checking each against both a white and a dark background, and labeling the ones that clear 4.5:1 as approved text colors in the token table. Anything that doesn’t clear the bar gets marked decorative-only, so nobody on the team accidentally reaches for it in a button label six months later.
Color contrast and legal risk
This isn’t only a design consideration. In the United States, ADA Title III web-accessibility lawsuits remain high in volume, and low contrast is one of the most commonly cited issues. The European Accessibility Act has been in force since June 2025, requiring WCAG 2.1 AA as a minimum for digital products and services reaching EU users. Any client with a public-facing site, especially one with e-commerce or booking functionality, carries real exposure here, not a hypothetical one.
One specific trap worth naming directly: accessibility-overlay widgets do not provide legal cover. Businesses that installed one got sued anyway, in large numbers, and a major overlay vendor was fined by the FTC for falsely claiming its tool made any site compliant. Fix the actual markup and colors. A widget bolted on top isn’t a fix, it’s a liability with a subscription fee.
Dark mode adds a second full pass, not a simple inversion. Flipping a light-mode palette’s values doesn’t guarantee the new pairings still clear AA, since text that read comfortably as dark-on-light can turn into a muddy, low-contrast mess as light-on-dark. Treat dark mode as its own contrast audit, with its own token pairings checked independently, rather than assuming the math carries over automatically.
Contrast beyond body text
Text gets most of the attention, but the 3:1 requirement for UI components and graphical objects covers a lot of ground people forget: input field borders, icon-only buttons, focus indicators, chart lines, and toggle states. An input field with a border that fails 3:1 against its background is functionally invisible to a low-vision user, even if the label text next to it passes with plenty of room to spare.
Never let color alone carry meaning either. An error state communicated only through a red border, with no icon and no text, fails for anyone who can’t perceive that particular shade of red as clearly different from the surrounding gray. Pair color with a second signal, an icon, a label, a pattern, every time. This is exactly the kind of check that belongs in a UI/UX review before anything ships, not a fix applied after launch. If your palette needs a rebuild, reach out and we’ll run the numbers before you touch a single hex code.
More web design & development guides
Ready to build the whole thing right?
One studio, one system, from first mark to full scale.