Skip to main content

Notes

Windows High Contrast Mode and forced-colors: What Breaks on an E-Commerce Site, and How to Fix It

Quietramp ·

Windows High Contrast Mode and forced-colors: What Breaks on an E-Commerce Site, and How to Fix It

Quick answer: Windows High Contrast Mode (and similar OS-level settings) trigger what CSS calls “forced colors mode” — the browser overrides most author-specified colors with a small, user-chosen palette. MDN’s own reference for the forced-colors media feature confirms this is triggered by “color scheme preferences in their operating system,” with Windows High Contrast mode as the named example. The override doesn’t just recolor things — it forces box-shadow and text-shadow to none outright, and forces any background-image that isn’t a url() to none as well. A “selected” swatch ring, a custom checkbox’s checkmark, or a color-only sale badge built with those properties doesn’t just lose contrast in this mode — it can disappear completely, with no fallback unless you’ve written one.

This article covers technical accessibility practice, not legal advice — it explains what a CSS-level browser mechanism does to common UI patterns and how to keep them visible, not whether a specific implementation carries legal risk.

What “forced colors mode” actually is

Forced colors mode is an operating-system accessibility feature, not a browser theme toggle. Windows High Contrast Mode is the feature most people mean when they use the term, and MDN describes the CSS media feature that detects it plainly: “the forced-colors CSS media feature is used to detect if the user agent has enabled a forced colors mode where it enforces a user-chosen limited color palette on the page.” A person turns this on in Windows accessibility settings — often because standard color combinations aren’t distinguishable enough for them, not because they want a stylistic dark mode — and every page they visit gets rendered through whatever palette they picked, whether or not the site’s own CSS was written with that in mind.

The media query itself, forced-colors: active, lets a stylesheet detect the mode and adjust. The values are binary: none (not active) or active (it is). Browsers support it in this form since roughly 2021–2022 across Chromium and Firefox.

What actually gets overridden — and what silently vanishes

This is the part that catches most sites off guard: forced colors mode doesn’t just apply a palette on top of your existing styles. Per MDN’s documented behavior, it overrides these properties at paint time with system colors: color, background-color, text-decoration-color, text-emphasis-color, border-color, outline-color, column-rule-color, and SVG fill/stroke. Those get recolored, not deleted — a link stays a link, just in whatever color the user’s palette assigns to links.

Three properties get a harsher treatment — forced to none entirely, not recolored:

  • box-shadow — forced to none. Any UI relying on a shadow or a shadow-as-border trick (a common way to draw a ring around a selected item without shifting layout) loses it completely.
  • text-shadow — forced to none.
  • background-image, for any value that isn’t a url() — forced to none. A CSS gradient used as a background (a common way to build a colored badge or button fill) disappears; an actual image file referenced by URL is left alone.

MDN’s own example shows exactly why this matters in practice: a button styled with box-shadow: -2px -2px 5px gray, 2px 2px 5px gray and no real border looks fine normally, but in forced-colors mode that shadow — the only thing giving the button a visible edge — is gone, leaving what looks like a blank, borderless rectangle.

Where this shows up on an e-commerce page

None of this is hypothetical for the patterns e-commerce sites use constantly:

  • Variant swatches. Quietramp’s own swatch accessibility article covers exposing selected-state through aria-pressed so assistive tech announces it correctly — but that’s a separate problem from the visual ring many sites draw around the selected swatch with box-shadow. Get the ARIA right and still lose the visual indicator in forced-colors mode if the ring itself depends on a shadow.
  • Custom checkboxes on filter panels. A checkmark or fill drawn with a background gradient or box-shadow instead of a real checked-state icon can vanish, leaving a checkbox that looks unchecked (or blank) regardless of its actual state.
  • Color-only sale badges. A “Sale” ribbon whose entire visual weight is a CSS gradient background with no border and no icon can disappear outright rather than merely lose contrast.
  • Custom focus rings built on box-shadow. If a site’s focus style is box-shadow: 0 0 0 3px blue instead of a real outline, that ring is gone in forced-colors mode too — on top of whatever contrast issues it already had. Quietramp’s focus indicators article already recommends outline over a custom shadow-based ring for unrelated reasons; a site that followed that advice is incidentally safer here, since outline-color is repainted with a system color rather than forced to none.

How this connects to WCAG

Forced colors mode isn’t named directly in WCAG SC 1.4.11 Non-text Contrast (Level AA), whose normative text requires a 3:1 contrast ratio for “User Interface Components [and] Graphical Objects” so they’re “distinguishable by people with moderately low vision.” But the Understanding doc’s own exemption is the relevant thread: contrast requirements don’t apply “where the appearance of the component is determined by the user agent and not modified by the author.” Forced colors mode is that exemption in action — the OS takes over color decisions specifically to guarantee sufficient distinguishability for someone who needs more than the default. The developer’s job is simply not to defeat that guarantee by hiding a component’s boundary entirely.

The sharper tie is SC 1.4.1 Use of Color (Level A): “color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.” A selected-swatch ring or a checked checkbox that exists only as a box-shadow was already one bad rendering context away from this failure — forced-colors mode just guarantees that context actually happens for real users, not as an edge case.

The fix: system color keywords inside a forced-colors block

The fix isn’t to prevent forced-colors mode from applying — it’s to give the browser something to work with instead of a property it’s about to erase. MDN’s <system-color> reference documents the keyword set browsers expose for exactly this: Canvas/CanvasText (page background/text), ButtonFace/ButtonText/ButtonBorder (control styling), LinkText, Highlight/HighlightText (selection), GrayText (disabled state), and others. These map to whatever palette the user actually chose, so the guidance is to build against them rather than guessing at colors that might not exist in someone’s theme:

.swatch-selected {
  box-shadow: 0 0 0 2px #1a56db; /* invisible in forced-colors mode */
}

@media (forced-colors: active) {
  .swatch-selected {
    /* box-shadow won't render here — use a real border with a system color instead */
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

This is the same fix MDN’s own documentation shows for a shadow-only button: replace the property that’s about to be zeroed out with a real border/outline using a system-color keyword, scoped inside the forced-colors: active block so nothing changes for users who aren’t in this mode.

The escape hatch, and why it’s not a default

forced-color-adjust lets an element opt out of forced-colors overrides entirely (none), inherit special handling (preserve-parent-color), or take the default browser behavior (auto). It exists for real cases — MDN’s own example is a product’s actual color swatches, where the color is the content being conveyed, not decoration. But MDN draws a direct usage boundary: this property should only be used to support a user’s own color and contrast needs — for example, tweaking a result that would otherwise render poorly in high-contrast or dark mode — and, in MDN’s own bolded words, “it should not be used to prevent user choices being respected.” Reach for it to preserve genuinely meaningful color (an actual color-name swatch), not as a default way to sidestep rewriting a component.

Can an automated scanner catch this?

No — and not just weakly. A direct check of axe-core’s own rule descriptions turns up zero rules mentioning forced colors or high contrast mode at all. That’s not a coverage gap in one rule — a standard automated scan runs in a normal browser context and never activates forced-colors mode in the first place, so this entire failure class sits outside what any markup-scanning tool observes. The markup can be perfectly valid — a well-formed <div> with a box-shadow and no ARIA errors — and still render as nothing once a real user’s OS setting kicks in.

How to test it yourself

You don’t need a Windows machine with High Contrast Mode enabled to check this during development. Chrome DevTools documents a built-in emulator: open the Rendering tab (via More Tools > Rendering, or the Command Menu), and under Emulate CSS media feature forced-colors, choose forced-colors: active. The page re-renders as it would under real forced-colors mode, which is enough to catch a vanishing swatch ring or badge without switching operating systems.

FAQ

Is this the same thing as dark mode? No. Dark mode (prefers-color-scheme: dark) is a stylistic preference the author still controls — you write the dark-theme colors. Forced colors mode takes color decisions away from the author almost entirely for the properties it governs; the user’s OS palette wins regardless of what CSS specifies, aside from the exceptions covered above.

If I already have good color contrast, am I safe? Not necessarily. Contrast ratio is about legibility once something renders. box-shadow and non-url() background-image don’t get a contrast problem in forced-colors mode — they get forced to none and stop rendering at all. A perfectly compliant contrast ratio doesn’t help if the element carrying it is gone.

Do real shoppers actually use this? Forced colors mode is a real, named operating-system accessibility feature (Windows High Contrast Mode) that Chromium and Firefox both built dedicated CSS support for specifically so sites could be tested and fixed against it — that engineering investment reflects genuine usage, not a niche edge case being humored.

Get a full audit that checks this by hand

Whether a selection ring, badge, or focus indicator survives Windows High Contrast Mode is a rendering-mode question a standard automated scan structurally can’t see — it has to be checked with forced-colors emulation turned on, by someone who knows what to look for. Quietramp’s audits pair an automated axe-core scan with a manual review that catches exactly this kind of gap, delivered as a prioritized, developer-actionable PDF report. See a real sample report or check pricing — $890 one-time, $99/month for ongoing re-checks after fixes ship.


This article is general accessibility information, not legal advice. Meeting WCAG 2.1 AA does not resolve ADA or any other legal compliance question, and nothing here should be read as a compliance guarantee. Consult qualified counsel for legal risk questions.

This article was drafted with AI assistance and reviewed by a person for accuracy before publication.

← Back to Notes