Français

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?

EnvironmentWho's behind the wheelMedia query
Windows — Edge, Chrome, FirefoxThe OS enforces colorsforced-colors: active
macOS — FirefoxThe browser offers the mode ("Colors" setting)forced-colors: active
macOS — Safari, Chrome, EdgeNo one forces colorsprefers-contrast: more
Linux — Firefox, ChromiumThe OS (high contrast theme) or Firefoxforced-colors: active
Android — Chrome, Edge, FirefoxNo one forces colorsno reliable lever
iOS — SafariNo one forces colorsprefers-contrast: more
iOS — Chrome, Firefox, EdgeNo 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 custom value of prefers-contrast does 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:

prefers-contrast.css
@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:

user-adaptation.css
@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