# High contrast mode hiding focus, icons and selections

Source: https://easeweb.dev/learn/forced-colors
Topics: Colour and contrast > Controls stay visible in forced colours (WCAG 1.4.11)
The fix: Either draw focus, state and icons with properties that a contrast theme keeps and recolours (an outline, a border, an icon in currentColor, a tick for the chosen option), or add a forced-colors block that redraws what the theme removes in system colours such as Highlight and ButtonText. Don't turn forced colours off with forced-color-adjust: none.
Test with: forced colors, keyboard, contrast measurement
References: https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html https://www.w3.org/TR/css-color-adjust-1/ https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/forced-colors https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/system-color https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A clothing shop's product page has a row of size chips, S, M, L and XL, and a heart button that saves the jumper to a wishlist. In the shop's own colours everything works: the chosen size is a dark chip with white text, focus is a blue ring, and the heart is a dark icon on white.

Now switch on a dark contrast theme in Windows, as many people with low vision do. Every chip turns black with a white border, so the chosen size looks exactly like the others. Tab to the chips or the heart, and no ring appears. The heart itself is gone: a gap where the button should be.

## Why it fails

A contrast theme, which CSS calls forced colors, replaces the page's colours with the user's own palette. Browsers do it property by property, and three of the shop's choices fall through:

- The chosen chip is marked only by `background-color`. The theme sets every background to the same canvas colour, so the one difference between chips is gone.
- The focus ring is a `box-shadow`, with `outline: none`. In forced colors, box shadows compute to `none`, and there is no outline left to recolour.
- The heart's path has `fill="#1a1a1a"`. Inline SVG keeps its own colours unless they come from the text colour, so a near-black icon sits on a black canvas.

WCAG 1.4.11 is measured in the page's own colours, and in those this page passes. But the criterion exists so people can identify controls and their states, and for someone using a contrast theme, this is the page they get. A field drawn only by a fill, with no border, disappears the same way.

## Who is affected

Windows contrast themes are the common source, through Settings, Accessibility, Contrast themes. Chromium browsers and Firefox on Windows follow them, and Firefox can also override page colours on any system. Some people use a dark theme because bright screens hurt, not because of low vision, and the page fails them in the same way.

## What automation and AI miss

Scanners and contrast checkers measure the colours the stylesheet sets. None of them turns a contrast theme on, so a page can pass every automated check and still lose its focus ring in one. Generated components make it more likely: Tailwind's `ring-*` utilities and most design-system focus styles are box shadows, often paired with `outline-none`. A state shown by a filled chip looks finished in every mock-up.

## Before: state, focus and icon drawn only in the page's colours

```html
<fieldset class="sizes">
  <legend>Size</legend>
  <input type="radio" name="size" id="size-m" value="m" checked>
  <label for="size-m">M</label>
  …
</fieldset>
<button class="save" aria-label="Save to wishlist">
  <svg viewBox="0 0 24 24" aria-hidden="true">
    <path fill="#1a1a1a" d="…"/>
  </svg>
</button>
```

```css
.sizes label {
  border: 1px solid #767676;
  background: #fff;
}
.sizes input:checked + label {
  background: #1a1a1a; /* the only sign of the chosen size */
  color: #fff;
}
.sizes input:focus-visible + label,
.save:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px #1a4fd0;
}
```

In the shop's colours each of these passes 1.4.11. Under a contrast theme the fill, the shadow and the icon's contrast are exactly what goes, so the state, the focus and the button are lost together.

## After: styles a contrast theme keeps

```html
<fieldset class="sizes">
  <legend>Size</legend>
  <input type="radio" name="size" id="size-m" value="m" checked>
  <label for="size-m">
    <svg class="tick" viewBox="0 0 24 24" aria-hidden="true">
      <path
        fill="none"
        stroke="currentColor"
        stroke-width="3"
        d="M5 12l5 5 9-10"
      />
    </svg>
    M
  </label>
  …
</fieldset>
<button class="save" aria-label="Save to wishlist">
  <svg viewBox="0 0 24 24" aria-hidden="true">
    <path fill="currentColor" d="…"/>
  </svg>
</button>
```

```css
.sizes label {
  border: 1px solid #767676;
  background: #fff;
}
.sizes .tick {
  display: none;
}
.sizes input:checked + label {
  background: #1a1a1a;
  color: #fff;
}
/* A second cue that survives any palette */
.sizes input:checked + label .tick {
  display: inline;
}
.sizes input:focus-visible + label,
.save:focus-visible {
  outline: 3px solid #1a4fd0;
  outline-offset: 2px;
}
.save {
  color: #1a1a1a;
}
```

A contrast theme recolours borders, outlines and the text colour rather than removing them. The tick is drawn in `currentColor`, so it shows in every palette, and it tells colour-blind visitors which size is chosen too. The focus ring is now an outline, which the theme draws in its own focus colour, and the heart follows the button's text colour wherever it goes.

## After: a forced-colors block in system colours

```css
/* The Before's styles stay as they are. */
@media (forced-colors: active) {
  .sizes input:checked + label {
    background: Highlight;
    color: HighlightText;
  }
  .sizes input:focus-visible + label,
  .save:focus-visible {
    outline: 3px solid Highlight;
    outline-offset: 2px;
  }
  .save path {
    fill: ButtonText;
  }
}
```

Browsers keep system colours that the page sets itself, so this block redraws exactly what the theme removed: the chosen chip in the user's highlight colour, an outline in that colour for focus, and the heart in their button text colour. The normal design doesn't change at all. It works only while the block keeps up with the components: a new chip style or icon needs its own line here.

## Which option to use

Both pass in every contrast theme. Styles the theme keeps need no second set of rules, so new components are covered as long as they follow the same habits, but the chosen chip gains a tick in the normal design. The forced-colors block leaves the design untouched and uses the user's own highlight colour, at the cost of a list that has to grow with every component. Many sites do both: they draw every focus ring as an outline and every icon in `currentColor`, which costs nothing, and use the block only for the states whose design can't change.

## Implementation decisions

If the design wants the box-shadow ring for its look, keep it and add `outline: 3px solid transparent`. A transparent outline is invisible in the normal colours, and the theme turns it into a visible one. Tailwind's `outline-hidden` does exactly this under forced colors; `outline-none` doesn't, so pair `ring-*` with `outline-hidden`.

The same habit helps filled buttons. A button with `border: none` becomes plain text in a contrast theme, while `border: 2px solid transparent` gives it back its shape at no cost to the normal design.

`forced-color-adjust: none` brings back the page's own colours, and with them the failure the user's theme was hiding from them. Use it only where the colour is the content, such as a colour swatch on a product, and give that swatch a border in `CanvasText` so it still has an edge.

Icons drawn as `background-image`, CSS gradients or `mask-image` go too: the theme drops background images that aren't files and recolours the mask's background to the canvas. Inline SVG in `currentColor` is the safest icon. An `<img>` icon keeps its own colours, so check it on both a dark and a light theme.

## Verify the fix

1. Turn on a dark contrast theme (Windows: Settings, Accessibility, Contrast themes, or in Chromium DevTools, the Rendering panel's "Emulate CSS media feature forced-colors"), and reload the page.
2. Tab through every control in both directions, and confirm the focused one is always marked.
3. Choose an option in every group, toggle every switch and visit the current page in the menu, and confirm each state shows without its fill.
4. Find every icon-only control, and confirm each icon is drawn.
5. Repeat with a light contrast theme, where dark fixed colours that vanished in the dark theme can reappear and light ones vanish.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after shared component styles or the icon set change.

## Limits

This is a starting pattern, not a guarantee for every application. Contrast themes differ between Windows versions and browsers, and Firefox's own colour override behaves slightly differently again. Normal-colour contrast is a separate requirement, covered in [low-contrast form fields, icons and focus rings](/learn/non-text-contrast). Test the complete pages with people who use a contrast theme every day.
