How to Choose Accessible Color Combinations
Color choices look like an aesthetic decision and act like a legal one. Roughly 4,000 web-accessibility lawsuits a year hit U.S. defendants since 2021, and inadequate color contrast is one of the three most-cited issues in WCAG-based filings (alongside missing alt text and unlabelled form fields). The good news is that contrast is the simplest of the three to fix: a contrast ratio is a number, the WCAG threshold is a number, and you can compute one against the other in seconds. The bad news is that "accessible" goes beyond the headline ratio: color blindness, focus indicators, dark mode, and tokenisation all matter, and a palette that scores 4.5:1 on every text style can still fail real users.
A short history of digital color accessibility
Color contrast as a written standard dates to the W3C's first Web Accessibility Initiative work in 1997. The original Web Content Accessibility Guidelines 1.0 (May 1999) recommended sufficient contrast in qualitative terms without a hard number. WCAG 2.0 (December 2008) introduced the now-familiar relative-luminance formula and the 4.5:1 ratio at Level AA, drawn from research by Lighthouse International on what reading-glasses-wearing older users could comfortably read on screens.
The legal weight came later. Section 508 of the U.S. Rehabilitation Act referenced WCAG 2.0 in 2018; the Americans with Disabilities Act, after the Robles v. Domino's Ninth Circuit ruling on 15 January 2019 (Supreme Court declined to hear the appeal in October 2019), is now read by U.S. courts as covering digital accessibility under Title III. The European Accessibility Act took effect 28 June 2025, requiring WCAG 2.1 AA on consumer-facing digital services for public-sector and many private companies. In practice, 4.5:1 normal text + 3:1 large text + 3:1 UI components is the de facto floor for any public website being built today.
The science has continued to evolve. WCAG 2.1 (2018) added the 3:1 non-text-contrast rule. The APCA algorithm, developed by Andrew Somers for WCAG 3, models brightness perception more accurately and gives different numbers for very dark and very light combinations; it is an Editor's Draft as of 2026, not yet a standard, so most regulation still cites WCAG 2.x. Bjorn Ottosson's OKLAB and OKLCH (2020) gave designers a perceptually-uniform color space that makes generating accessible color scales much easier than working in HSL or RGB.
What "accessible color" really means
Four things matter, not just the one ratio:
- Foreground vs background contrast. The headline number. Body text needs 4.5:1, large text 3:1, UI controls and focus indicators 3:1 against adjacent colors. Compute the ratio with the WCAG 2 formula in any contrast checker.
- Color blindness. Roughly 8 % of men and 0.5 % of women see fewer colors than the design assumes. The three main types are protanopia and deuteranopia (red-green, the most common), and tritanopia (blue-yellow). A red error label on a green success label can be invisible to a protanopic viewer.
- Color is never the only signal. WCAG 1.4.1 forbids relying on color alone to convey meaning. A red "error" border needs an icon or a label too. A green "success" toast needs a checkmark.
- Focus indicators. Keyboard users need to see where focus is. The WCAG 2.4.7 rule requires a visible focus indicator, and the 2.4.11 update (WCAG 2.2) requires it to be at least 2 CSS pixels thick and 3:1 contrast against the adjacent color.
A palette that gets the ratio right but ignores the other three still fails real users.
How to choose accessible colors, step by step
- Start with semantic tokens, not raw colors. Define
text-primary,text-secondary,surface-default,surface-elevated,border-default,accent-primary,accent-danger, etc. Map each token to a specific color, and let those tokens carry the semantic meaning everywhere in the design system. Changing a color later then becomes a one-line token edit, not a hunt through stylesheets. - Pick the lightness scale. Use OKLCH lightness values to build a 5-9 step scale (50, 100, 200, ..., 900) where each step has a constant perceptual difference from the next. The numbers will look unusual in OKLCH (around L = 0.97, 0.93, 0.88...) but the perceived steps will be even, which is impossible in HSL.
- Choose hues at each lightness step. Pick the brand hue (say, blue around 240 degrees in OKLCH), then pick complementary or accent hues that maintain similar chroma. Use OKLCH so that "100 saturation blue" and "100 saturation orange" look the same brightness.
- Compute the contrast ratios. Every token pair that touches needs the right ratio. Body text on default surface: 4.5:1. Disabled text on default surface: still 3:1 if it needs to be readable (4.5:1 if regulators are strict). Focus indicator on adjacent surface: 3:1.
- Run a color-blindness simulator. Take a key screen and re-render it through protanopia, deuteranopia, and tritanopia simulators. Confirm that information conveyed by color is still distinguishable.
- Test with real assistive technology. Mac VoiceOver, NVDA, JAWS, and TalkBack should all be able to navigate without depending on color cues.
- Add the non-color signal everywhere color carries meaning. Icons for error/success/warning, underlines for links in body text, patterns for chart series.
- Document the tokens. A Figma library, a Storybook page, or a static design-system site. Auditors and new designers should be able to pick the right token in seconds.
Contrast ratio reference
| Pairing | WCAG 2.1 minimum | Level AA | Level AAA |
|---|---|---|---|
| Body text (under 18 pt regular or 14 pt bold) | 4.5:1 | 4.5:1 | 7:1 |
| Large text (18 pt regular or 14 pt bold) | 3:1 | 3:1 | 4.5:1 |
| UI components (form borders, toggle states, focus indicators) | 3:1 | 3:1 | n/a |
| Graphical objects (icons that carry meaning) | 3:1 | 3:1 | n/a |
| Text inside a logo | exempt | exempt | exempt |
| Disabled controls | exempt | exempt | n/a |
| Decorative text | exempt | exempt | exempt |
The exemption for disabled controls is a legal carve-out, not a usability blessing; a disabled control that nobody can read may technically pass WCAG but will still confuse users.
Color blindness types and how to check
| Type | Prevalence | Sees less of | Common failure |
|---|---|---|---|
| Protanopia | ~1 % of men | Red | Red error vs green success indistinguishable |
| Deuteranopia | ~5 % of men | Green | Same: red-green confusion |
| Tritanopia | ~0.005 % | Blue | Blue vs yellow indistinguishable |
| Achromatopsia | ~0.003 % | All color | Sees only grayscale luminance |
| Anomalous trichromacy | ~5 % | Reduced sensitivity in one channel | Subtle color differences merge |
Always run the design through at least protanopia and deuteranopia simulators; they catch the majority of color-blind users. Achromatopsia is rare but tests whether your design degrades to pure luminance contrast, which it should.
Common pitfalls
- Designing in HSL or RGB. Equal numeric steps in HSL produce unequal perceived steps. A 10 % brightness jump in pale yellow looks much bigger than the same jump in dark blue. OKLCH fixes this.
- Relying on color alone to convey state. Red and green for error and success: 8 % of users cannot tell them apart. Add an icon or a label.
- Pure black on pure white.
#000on#FFFscores a perfect 21:1 ratio but is harsh on the eyes for long reading; users with astigmatism or visual stress conditions find it tiring. Drop to a near-black (#1a1a1aor OKLCH(0.18 0 0)) and a near-white (#f8f8f8) for body text. - Brand-color tyranny. Treating the brand red as sacred and using it on the brand white at 2.1:1 contrast just because "that is the brand." Refresh the brand palette to one that meets WCAG; major brands (Slack, Stripe, GitHub) have all done this in the last decade.
- Focus indicator that disappears on dark backgrounds. A 2-pixel blue ring against a dark blue button might be invisible. Test the focus ring against every surface where it might appear.
- Placeholder text that fails contrast.
placeholder=text inside an input is rendered by the browser at ~40 % opacity. On a white input, that easily drops below 4.5:1. - Form field outlines that are too faint. A 1px grey-on-white border at 1.2:1 contrast fails WCAG 1.4.11 (non-text contrast). Bump to 3:1 or thicker.
- Hover states that change only color. Some pointer-input devices (Bluetooth styluses, eye-tracking) do not produce hover; a hover-only color change is invisible to them. Combine with a subtle shadow or transform.
- Dark mode by simple inversion. Inverting a light palette produces saturated dark colors that look harsh. Dark mode palettes need their own design pass with desaturated accents.
- Auto-generated text from images. Text rendered on top of a user-uploaded image (cover photo, hero banner) cannot guarantee contrast. Add a translucent backdrop or text-shadow that brings the local contrast back up.
Alternative algorithms and standards
WCAG 2's relative luminance formula is not the only game in town.
| Algorithm | Year | Status | Strength | Weakness |
|---|---|---|---|---|
| WCAG 2.x contrast ratio | 2008 | Required by most regulation | Simple math, widely tooled | Inaccurate for very dark and very light pairs |
| APCA (Accessible Perceptual Contrast Algorithm) | 2020 | WCAG 3 Editor's Draft | Better at perceptual brightness | Not yet legally required, separate thresholds |
| ISO 9241-302 | 2008 | Workstation ergonomics standard | Hardware-oriented, very strict | Limited adoption in web tools |
| ANSI/HFES 100 | 2007 | U.S. ergonomics | Similar to ISO | Limited adoption |
| Lighthouse contrast warnings | 2018 | Chrome DevTools | Free, in-browser | Uses WCAG 2 by default |
| axe-core | 2015 | Open-source library | Industry standard for automated testing | WCAG 2 only, not APCA |
For regulated work today, WCAG 2.1 AA is the floor. APCA is worth tracking as WCAG 3 matures; many design-system teams already report both numbers.
Tools for picking accessible colors
| Tool | Purpose | Strength |
|---|---|---|
| Color contrast checker (browser) | Score a foreground/background pair | Instant, no upload, accessible from any device |
| Accessible palette generator | Build a full lightness scale at constant chroma | Uses OKLCH for perceptual uniformity |
| Color blindness simulator | Re-render a screen as a color-blind viewer sees it | Catches red-green issues before launch |
| Chrome DevTools Lens | Inspect contrast and color-blindness in place | Built into the browser |
| WebAIM Contrast Checker | Classic WCAG 2 reference | Trusted, widely cited |
| Stark (Figma plugin) | Audit Figma designs for contrast and blindness | Catches issues at design time |
| axe DevTools (browser) | Automated WCAG scan | Industry standard |
| Tailwind, Radix, Open Props color scales | Pre-tested OKLCH-based palettes | Accessibility curated by design teams |
For new projects, start from a tested palette (Radix Colors and Open Props are good defaults), pick brand tokens, and verify every text-on-surface pair with a contrast checker. For existing projects, do a one-time audit of every text/background pair in your stylesheet, fix the failures, and add a Stark or axe step to your CI so the next failure is caught at PR time.
Privacy and the tools
The color contrast checker, accessible palette generator, and color blindness simulator all run entirely in your browser. The colors you pick or paste are processed by JavaScript on your device, results are rendered to the page, and nothing is sent to a server. No telemetry, no analytics on the input, no third-party scripts. For brand colors that are not yet public, internal product palettes, or designs under embargo, that local-only flow is the difference between trusting a stranger's design tool with your unreleased brand assets and not. The tools can run offline once the page is loaded, which you can verify by switching off your network and re-checking a contrast pair.
Frequently Asked Questions
What contrast ratio does WCAG require?
WCAG 2.1 Level AA requires at least 4.5:1 for normal body text and 3:1 for large text (18 pt regular or 14 pt bold). Level AAA raises that to 7:1 for normal text and 4.5:1 for large text. Non-text UI components and graphical objects need 3:1 against adjacent colors.
Are WCAG contrast ratios scientifically accurate?
They are based on the WCAG 2.0 algorithm from 2008, which compares the relative luminance of two colors using the sRGB color space. The formula is imperfect for very dark and very light combinations and for some color hues; the APCA (Accessible Perceptual Contrast Algorithm) being developed for WCAG 3 produces more perceptually accurate results. For 2026 work, WCAG 2.x is still the standard most regulations cite.
How do I check whether my colors work for color-blind users?
Run them through a color-blindness simulator that converts your palette to what someone with protanopia, deuteranopia, or tritanopia sees. About 8% of men and 0.5% of women have some form of color vision deficiency, so this matters.
Should I use the same color palette for light and dark mode?
Usually not. Dark mode needs slightly desaturated colors at the same hue, otherwise vivid colors that look fine on white become eye-strain on black. Define semantic tokens (text-primary, surface-secondary) and map them to different physical colors per mode.
What does OKLCH have to do with accessibility?
OKLCH is a perceptually uniform color space (Bjorn Ottosson, 2020) where equal numerical changes produce equal perceptual changes. That makes it much easier to generate accessible color scales because you can step the lightness in equal increments and the perceived brightness step is consistent, which is hard to do in HSL or RGB. CSS supports OKLCH directly since 2023 in all major browsers.