Perfect contrast does not exist
Table of contents
You did the work, and it's not enough
In Colors lie, I showed how to build a design that tells the truth: pair every color signal with a text signal, check contrast ratios, test in forced-colors. You may have applied all of that. You aim for AAA, 7:1 everywhere. Your validator is green.
And yet, there are still people for whom your colors don't work.
Take incomplete achromatopsia. These people retain some residual color vision, but it's reduced, and above all different from one person to the next. Some can still make out a trace of red, others a hint of blue, depending on which cones still function. There is no single achromatopsia: there are as many as there are people affected.
For them, the WCAG contrast ratio does not describe their reality. The formula is calculated for typical three-cone vision. Two colors the standard deems perfectly contrasted can appear identical to them. And the reverse is also true: two colors deemed similar can look distinct. The perfect ratio on paper guarantees nothing in their perception.
We measure, we validate, we check the box. And there are still people for whom it doesn't work, because no statistical measure can cover a perception this singular.
So do we search for the magic color? It doesn't exist. The real answer is not to choose better. It's to let them choose.
Who's behind the wheel
When a user activates forced-colors mode, they define their own colors: text, background, links. The browser applies them to your site, overriding your CSS. You detect it with forced-colors: active and stay consistent using system color keywords like Canvas or CanvasText.
The question is: who provides this mechanism?
| Environment | Who's behind the wheel | Media query |
|---|---|---|
| Windows — Edge, Chrome, Firefox | The OS enforces colors | forced-colors: active |
| macOS — Firefox | The browser offers the mode ("Colors" setting) | forced-colors: active |
| macOS — Safari, Chrome, Edge | No one forces colors | prefers-contrast: more |
| Linux — Firefox, Chromium | The OS (high contrast theme) or Firefox | forced-colors: active |
| Android — Chrome, Edge, Firefox | No one forces colors | no reliable lever |
| iOS — Safari | No one forces colors | prefers-contrast: more |
| iOS — Chrome, Firefox, Edge | No one forces (WebKit engine imposed) | prefers-contrast: more |
The dividing line is not Windows versus macOS. It's: who's behind the wheel. On Windows, the OS. On macOS and Linux with Firefox, the browser. Everywhere else, Chrome, Edge, Safari and the entire mobile ecosystem, no one forces anything.
The last row in the table deserves a closer look. Chrome on iPhone, Firefox on iPad: these are shells around Apple's WebKit engine. Whatever the icon on the home screen, Safari does the rendering. And Safari, as the WebKit team documents clearly:
The
customvalue ofprefers-contrastdoes not return "true" on Apple platforms, since there is no forced-colors mode in iOS, iPadOS, macOS or visionOS.
In Safari, an unsatisfied media query simply evaluates to false: forced-colors: active will never be true. There is nothing to activate.
European regulation (DMA) allows alternative browser engines on iOS in the European Union since iOS 17.4. But to date, no browser has shipped a version using a non-WebKit engine. The table above remains accurate.
On Safari, all you get is prefers-contrast: more. Not a palette, just an intent: "I want more contrast." And it's up to you to respond.
Don't get in the way
Your role is not to pick the right contrast. It's to not prevent the user from picking their own.
For forced-colors: active
This is the case covered in Colors lie. When forced-colors mode is active, the browser replaces your palette. Your job is to make sure nothing essential relies on what it can't replace on its own: information carried by a background-image, a shadow, a border you thought was decorative.
Turn on high contrast mode on your machine. Browse your own site. What you see will probably surprise you.
For prefers-contrast: more
This is the safety net for Safari and the entire Apple mobile ecosystem. The user isn't redefining your colors; they're telling you they need better ones. It's up to you to respond, within your own design:
@media (prefers-contrast: more) {
:root {
--color-border: var(--color-text);
--color-bg-subtle: var(--color-bg);
}
.btn-secondary {
border-width: 2px;
}
}Thicken borders, remove subtle backgrounds that muddy readability, strengthen visual separations. Not a redesign, just a few targeted adjustments that make the difference for those who asked.
Both together
The two media queries don't conflict. They cover different cases, and sometimes the same ones: when Firefox forces colors, it also sets prefers-color-scheme and prefers-contrast based on the chosen theme, and on Windows both are active at the same time. Add them to your CSS foundation alongside your other user adaptation media queries:
@media (prefers-reduced-motion: no-preference) { /* animations */ }
@media (prefers-contrast: more) { /* … */ }
@media (forced-colors: active) { /* … */ }If these lines are in your foundation from day one, every component you build will be designed to respond.
The best contrast is the one you don't decide for someone else.
Sources
- WebKit — Features in Safari 18.0 — documenting the absence of forced-colors on Apple platforms
- Apple Developer — Alternative Browser Engines — DMA framework for alternative engines on iOS
- MDN — forced-colors — specification and browser compatibility
- Polypane — Forced colors explained — practical guide, including Firefox's own mode
- MDN — prefers-contrast — contrast intent media query
- MDN — CSS system-color — system keywords for forced-colors