Français

Colors lie

Table of contents

The border that doesn't change

You're coding a form. The email field is required, the label is in place, native HTML validation does its job. When the user forgets to fill in the field, the border turns red, exactly as the mockup shows. You test it, it works, the designer signs off, you move on.

Except that for 1 in 12 men, that border hasn't changed color. Red and gray look the same. The field is invalid, but nothing says so. And no one will come to complain, 56% of unhappy users say nothing, they quietly switch to a competitor.

Colors lie. Not because they're wrong, but because they give you the illusion that information is getting through, when it stops halfway for part of your audience.

This article will show you who your colors lie to, why the problem is more widespread than you think, and how to build interfaces that tell the truth to everyone.

What your eyes don't show theirs

Color blindness isn't seeing in black and white. It's a spectrum of different perceptions, and it affects far more people than most realize.

300 million people worldwide don't perceive colors correctly. 8% of men. 0.5% of women. Out of the 200 people who will visit your page today, 8 or 9 will see your interfaces differently from what you intended.

The most common forms, protanopia and deuteranopia, affect the perception of red and green. 1 in 12 men. These are precisely the colors the industry uses everywhere to signal errors and success. The red "danger" and the green "OK" are invisible to those who need them most.

There's a well-known anecdote about this: Mark Zuckerberg chose blue for Facebook because it's the color he perceives best, he's color blind. And he was right to adapt: blue is the most resilient color against color blindness. The forms that affect blue perception (tritanopia) affect about 1 in 10,000 people, compared to 1 in 12 men for red-green.

But the real lesson from this anecdote isn't "use blue." It's that color is a communication channel that doesn't work the same way for everyone. And if it doesn't work for 8% of the male population, it's not an edge case, it's a flaw in the design.

The test nobody runs

The WebAIM Million 2026 report analyzed 1 million homepages. The number one accessibility problem, year after year: insufficient contrast. 83.9% of pages are affected, with an average of 34 contrast errors per page.

What makes this figure hard to accept is that these errors are automatically detectable. No audit needed. No expert needed. No budget needed. WAVE, the tool WebAIM used for this study, is a free browser extension. You install it, open it on your page, and problems appear directly on the affected elements, with the measured ratio and the required minimum.

10 seconds. That's how long it takes to find out if your page is lying about its colors.

But what exactly is a contrast ratio? It's not an arbitrary number invented by a committee. It's a measurement of the luminance difference between two colors: the text and its background. The higher the ratio, the more readable the text remains when conditions degrade.

WCAG defines two conformance levels for contrast. AA — 4.5:1 is the minimum for standard text. It's the legal floor, and enforcement is accelerating. The ADA Title II rule explicitly requires WCAG 2.1 AA for public sector websites. And while no formal rule exists yet for private businesses under Title III, courts are applying the same standard: 3,117 federal web accessibility lawsuits were filed in 2025 alone, up 27% from 2024.

For large text (24px or above, or 18.66px bold), the threshold drops to 3:1. Larger characters naturally compensate for lower contrast.

Beyond AA, WCAG defines an enhanced level: AAA, at 7:1 for standard text and 4.5:1 for large text. Not legally required, but it provides a noticeably more comfortable reading experience, especially in degraded conditions: screens in direct sunlight, end-of-day eye fatigue, aging monitors, or eyes that don't perceive every nuance. That's what Bariafuri aims for.

A concrete example: gray text #767676 on a white background #FFFFFF gives a ratio of 4.54:1, barely AA. It passes the threshold, but it's uncomfortable to read on an average screen. Move to #595959 and you reach 7.00:1, AAA right at the threshold. The difference is subtle to the eye, but real over prolonged reading.

You can check your combinations in seconds with the WebAIM Contrast Checker or Tanaguru Contrast Finder, the latter automatically suggests an alternative color if your combination fails.

Color must never be alone

WCAG sums it up under Guideline 1.4 "Distinguishable." The criteria that directly concern color and contrast:

  • Criterion 1.4.1 — don't use color as the only means of conveying information (Level A)
  • Criterion 1.4.3 — minimum contrast for text: 4.5:1 standard, 3:1 large (Level AA)
  • Criterion 1.4.6 — enhanced contrast for text: 7:1 standard, 4.5:1 large (Level AAA)
  • Criterion 1.4.11 — contrast for UI components and graphical elements: 3:1 (Level AA)

Not "don't use color." Not "colors are bad." Just: don't leave it alone. Double every color signal with a signal that doesn't depend on hue perception.

Let's go back to the form from the introduction. The red border isn't enough. Here's what is:

form-validation.html
<form>
  <label for="email">Email address</label>
  <input
    id="email"
    type="email"
    required
    aria-describedby="email-error"
  />
  <p id="email-error" class="error-message" aria-live="polite">
    This field expects an email address in the format name@domain.com
  </p>
</form>
form-validation.css
input:not(:user-invalid) + .error-message {
  display: none;
}
 
input:user-invalid {
  border-color: var(--color-error);
}
 
.error-message {
  color: var(--color-error);
  font-size: var(--font-size-sm);
}
 
.error-message::before {
  content: "⚠" / "";
  margin-inline-end: var(--space-1);
}

The color is still there, the border changes for those who perceive it. But it's no longer alone. The text message says what's wrong. The ⚠ icon as a CSS pseudo-element visually signals the problem without depending on color, and without polluting the HTML. Watch out for the accessibility tree, though: modern screen readers do read CSS-generated content, and the "⚠" would be announced as "warning sign" in the middle of the message. The alternative text syntax content: "⚠" / "" declares it decorative: the message text is enough. And :user-invalid, unlike :invalid, only triggers after user interaction, not on page load.

One caveat about the spoken announcement: revealing a pre-filled message by toggling its display is unreliable across screen readers, the live region doesn't always detect the change. The robust pattern, injecting the text with JavaScript into a container that's already present, is detailed in Beware of the Leopard, part 2.

This pattern, color + text + icon, applies everywhere:

A link that only stands out from text by its color is invisible to a color blind person. The simplest and most robust solution:

links.css
a {
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: var(--underline-offset);
}

The underline doesn't depend on any color perception. That's why it has existed since the early days of the web, and removing it is almost always a mistake.

Charts and visualizations

A bar chart whose series are only distinguished by color is unreadable for part of your audience. Three complementary solutions:

  • Textures or patterns in addition to colors (hatching, dots, solid fill)
  • Labels directly on elements, not only in a separate legend
  • A text legend that doesn't rely on color-to-meaning mapping

Interface states

Active vs inactive button, selected vs unselected tab, enabled vs disabled element: if the only difference is color, the signal is lost. Combine:

  • Color + border or outline
  • Color + text label ("Active", "Selected")
  • Color + icon change
  • Color + typographic variation (bold, style)

The rule is simple: if you remove color from your interface and the information disappears, it was alone. And if it was alone, it was lying to part of your audience.

When the user takes back control

Everything we've seen so far assumes your colors reach the user. But some users don't give you that choice. They impose their own.

Windows, macOS, and Linux offer high contrast modes. The user picks a theme with a handful of colors, sometimes 8, sometimes fewer, and the browser replaces your palette with theirs. Automatically. Without asking your permission.

This isn't a bug. It's a feature designed for people who simply cannot use your palette: severe visual impairment, photosensitivity, chronic eye fatigue. For them, it's this or not using the web.

And when this mode activates, things break in your CSS.

box-shadow disappears. Gradients are flattened. Subtle borders become invisible. And bicolor SVG icons can become a uniform block when all their colors are replaced by the same system value.

That's where @media (forced-colors: active) comes in: not to redefine your entire palette, but to fix what breaks:

forced-colors-fixes.css
/* A button that relied on a box-shadow
   to stand out → we add a real border */
@media (forced-colors: active) {
  .btn {
    border: var(--border-width) solid ButtonText;
  }
}

ButtonText, Canvas, Highlight… CSS provides about twenty system keywords that point to the colors chosen by the user. You no longer decide which colors to apply. You decide which roles your elements play, and the browser assigns the right color.

For SVG icons, the simplest solution:

svg-icons.css
.icon {
  fill: currentColor;
}

currentColor ties the icon color to the surrounding text color. In normal mode, it follows your design. In forced-colors, it follows the user's theme. One keyword, zero maintenance.

For multi-color SVGs (a logo, a diagram), it's trickier. Two options:

  • Remap each SVG element to distinct system colors (ButtonText, Highlight, LinkText)
  • As a last resort, forced-color-adjust: none to keep the original colors, but only if you've verified the SVG remains readable on the new background
svg-multicolor.css
/* Last resort — keep original colors */
.logo-multicolor {
  forced-color-adjust: none;
}

forced-color-adjust: none is an opt-out. You're telling the browser: "I'll handle it myself." That's a responsibility, not a shortcut. If you use it, you test it.

Most developers have never activated high contrast mode on their machine. If that's you, I invite you to do it now, even for just 5 minutes, and browse your own site. What you see will probably surprise you.

Building a design that doesn't lie

The previous sections gave you point fixes: an aria-describedby here, a forced-colors there. But fixing case by case is playing whack-a-mole. The real change is integrating these reflexes upstream, in the way you design.

Start in grayscale

Before applying your palette, look at your interface without color. Open Chrome DevTools, Rendering tab, and enable the "Achromatopsia" simulation. If your interface works in grayscale, if errors are identifiable, links are spottable, states are distinguishable, then the color you add next will be a bonus, not a crutch.

Test what your users see

DevTools offer several vision deficiency simulations: protanopia, deuteranopia, tritanopia, blurred vision. Enable them on your page during development, not at the end of the project. 30 seconds is enough to spot a signal that relies solely on color.

And to go further than simulation, activate your OS's high contrast mode and browse your own site. That's often where you discover the box-shadow that served as a border, the SVGs that disappear, and the visual subtleties that weren't.

Integrate forced-colors into your design system

Don't treat forced-colors as a patch you add at the end. Add it to your base file, alongside your other adaptation media queries:

base-theme.css
/* Dark mode via light-dark() — no media query needed */
:root {
  color-scheme: light dark;
  --color-bg: light-dark(#F0FDFA, #0D1B19);
  --color-text: light-dark(#134E4A, #CCFBF1);
}
base-media-queries.css
/* Your 3 user adaptation media queries */
@media (prefers-reduced-motion: reduce) { /* … */ }
@media (prefers-contrast: more)         { /* … */ }
@media (forced-colors: active)          { /* … */ }

light-dark() replaces prefers-color-scheme for color values: the browser picks the right value based on the active theme. For everything else (conditional images, shadows, theme logic), the media query remains necessary. The other 3 media queries have no functional equivalent and remain necessary. If these lines are in your foundation from day one, every component you build will be designed to respond to them.

Check with the right tools

No audit needed to get started. These tools are free and give results in seconds:


And to conclude, a final reminder. Colors are not the enemy. They're a powerful tool, when they're not alone. A red error accompanied by a message, an icon, and an ARIA attribute is still an effective red error. It simply tells the truth to everyone, not just those who perceive it.

Building a design that doesn't lie isn't about giving up colors. It's about stopping them from carrying alone a message they can't guarantee.

The overall criteria

These criteria cover everything this article describes: never convey information through color alone, ensure sufficient contrast for text and components, and offer an enhanced mode for those who need it.

Sources