CSS forced-colors media query for Windows High Contrast
Windows High Contrast support without broken UI
Windows High Contrast and similar forced-color modes ignore most of your CSS. Custom backgrounds vanish, drop shadows disappear, and decorative borders get stripped. forced-colors lets you opt back in with system colors that the OS guarantees will be readable.
/* no forced-colors handling: in High Contrast mode the background is stripped and the transparent border becomes invisible. The button disappears. */.btn { background: var(--accent); color: white; border: 1px solid transparent;} .btn { background: var(--accent); color: white; border: 1px solid transparent;}@media (forced-colors: active) { .btn { background: ButtonFace; color: ButtonText; border: 1px solid ButtonText; forced-color-adjust: none; }}/* High Contrast users see a real button instead of a coloured rectangle */ forced colors Browser Support
This feature is well established and works across many devices and browser versions. It has been available across browsers since 2022.
Safe to use without fallbacks.
Buttons stay buttons
In forced-colors mode the OS strips backgrounds and shadows. Without an opt-in, the button you styled with a coloured background and no border becomes invisible. ButtonFace and ButtonText guarantee the OS will render them with a readable contrast pair.
Pairs with prefers-contrast
Use @media (prefers-contrast: more) for users who asked for higher contrast but are not in forced-colors mode. The two queries cover different audiences. Stack them when both matter.
Opt out of overrides
forced-color-adjust: none tells the browser "trust me, I have already adapted these colours". Use it sparingly and only after you have confirmed the contrast still works.
How it works
Windows High Contrast (now called Contrast Themes) and similar modes on other platforms force a small palette of OS-defined colours onto every page. The browser strips most background images, drop shadows, and custom borders. If your button relied on a coloured background with no visible border, it becomes a transparent rectangle.
The fix is two queries used together. @media (forced-colors: active) fires only when the OS is in a forced-colors mode. Inside it, set the colours that matter using system color keywords like ButtonFace, ButtonText, LinkText, Highlight, and HighlightText. These map to whatever palette the user picked, so you do not need to know in advance which colours they want.
forced-color-adjust: none opts a specific element out of the OS overrides. Reach for it only when you have already paired the foreground and background using system colours, otherwise you are taking responsibility for accessibility the browser was handling for you.