forced-colors Mode: When Windows Takes Your Palette
High Contrast and forced-colors strip your brand colors. System colors, forced-color-adjust, and what still works.
Your navy sidebar, coral CTAs, and carefully tuned gray scale disappear. The user enabled Windows High Contrast, or Edge forced colors, or a similar operating-system accessibility mode, and the browser replaced your palette with system colors: black text on white fields, yellow links, button faces the theme defines. This is not a bug in your CSS. It is a contract between the operating system and users who need predictable, high-legibility rendering regardless of what marketing shipped last quarter.
If you have only tested light mode and dark mode, forced-colors is the third rendering path that breaks gradient heroes, status pills colored only by background-color, and focus rings that relied on box-shadow instead of solid outlines. The forced-colors media query is how CSS acknowledges that contract. This essay covers what the mode does, which system color keywords to use, how forced-color-adjust fits in, which patterns collapse under mandatory palette substitution, and how to test without guessing from DevTools alone.
What forced-colors actually forces
The CSS Media Queries Level 5 forced-colors feature detects when the user agent applies a forced colors palette, typically from Windows High Contrast themes, though other platforms implement analogous behavior. When active, browsers map user interface colors to a small set of system colors instead of your authored values for many paint-related properties.
Forced colors mode prioritizes readability over brand expression, consistency with native controls and other applications, and respect for user-chosen themes such as High Contrast White, High Contrast Black, and custom contrast schemes shipped with the operating system. Properties commonly overridden or limited include color, background-color, border-color, outline-color, and in some cases SVG fills. Gradients, decorative background images, and subtle gray hierarchies may collapse to flat system colors. Properties that do not affect legibility as directly, including display, grid, flex, spacing, and typography size, generally remain intact.
The media query branch you must style is forced-colors: active. The forced-colors: none value matches when the mode is off. Do not conflate this with prefers-contrast: more, which is a hint that users want stronger differentiation. You may thicken borders or darken text under prefers-contrast, but your palette still applies. Forced-colors is mandatory substitution at the user-agent level. A site can respect both by tuning brand tokens under prefers-contrast: more while switching to system color keywords under forced-colors: active.
W3C’s Understanding SC 1.4.11 Non-text Contrast and broader WCAG 2.2 guidance assume authors provide sufficient contrast in default presentation. Forced-colors users delegate much of that responsibility to the OS theme they selected deliberately. Fighting the mode to preserve brand purple hurts accessibility rather than helping it.
@media (forced-colors: active) {
.card {
border: 1px solid CanvasText;
}
}
Older Microsoft documentation referenced @media (-ms-high-contrast: active). Modern Edge and Windows use the standard forced-colors query. Maintain forced-colors as primary. Keep the legacy query only if analytics show meaningful EdgeHTML traffic, a shrinking cohort in 2026.
System colors as your palette
CSS defines system color keywords that resolve to the forced palette. They are the vocabulary for forced-colors-safe styling. The CSS Color Module Level 4 system colors and MDN’s system-color keyword reference document the full set. The keywords you reach for most often include Canvas for the background of the content area, CanvasText for text on that background, LinkText and VisitedText for link states, ButtonFace and ButtonText for buttons, Field and FieldText for inputs, Highlight and HighlightText for selected items, and GrayText for disabled text.
Use these instead of hex when forced-colors is active. Your primary button that normally uses brand fill should surrender to system button colors with a visible border so shape survives palette flattening. Links should not rely on color alone in any mode. Under forced-colors, LinkText and VisitedText differentiate states the operating system expects.
Disabled controls map to GrayText in many themes. Do not pair GrayText with opacity reductions that were already marginal in default mode. Selected list items should use Highlight and HighlightText rather than a brand-tinted background at ten percent opacity that vanishes entirely under substitution.
.btn-primary {
background: var(--brand);
color: white;
}
@media (forced-colors: active) {
.btn-primary {
background: ButtonFace;
color: ButtonText;
border: 2px solid ButtonText;
}
a:any-link {
color: LinkText;
}
a:visited {
color: VisitedText;
}
}
ButtonText on ButtonFace does not guarantee WCAG contrast against arbitrary custom High Contrast themes. The user chose the theme because it works for them. Your job is not to restore brand blue. Your job is to avoid layouts that break when colors flatten: invisible icons, lost focus, unreadable placeholders, and status communicated only by hue.
forced-color-adjust and when to opt out
The forced-color-adjust property tells the user agent whether to apply forced-colors mappings to an element. The default auto means the element participates in the forced palette. The preserve-parent value inherits the parent’s adjustment behavior. The none value opts out and keeps authored colors where the user agent allows.
Use none sparingly: logos, charts where color encodes required meaning and system colors would misrepresent data, or photographic content where flattening destroys comprehension. Abuse none to save a purple marketing banner and you recreate the illegibility users enabled High Contrast to escape. For data visualization, prefer patterns, labels, and icons in addition to color so forced-colors mode does not strip meaning. If a chart series must keep hue, document why and test with multiple Windows contrast themes.
The preserve-parent keyword exists for elements whose forced-color behavior should match a wrapping component, such as a custom widget that intentionally inherits from a parent card already tuned for forced-colors. Most leaf components never need it. Reach for auto and explicit system colors first before opting any subtree out of the forced palette.
@media (forced-colors: active) {
.brand-logo-svg {
forced-color-adjust: none;
}
:focus-visible {
outline: 3px solid Highlight;
outline-offset: 3px;
}
}
Focus indicators deserve special attention. An outline: none reset paired with a pretty box-shadow focus ring may disappear under forced-colors. Prefer visible outline in the forced-colors branch, using system Highlight where appropriate, aligned with Understanding Focus Visible. Native checkboxes, radios, and selects receive system styling in many forced-colors implementations. Custom-styled toggles that hide native inputs may still look custom or broken. If your toggle is a div with role="switch", ensure state is exposed to assistive technology and visible without relying on hue alone.
Patterns that break under mandatory substitution
Hero sections with photographic overlays and gradient scrims often fail legibility when flattened. Provide a forced-colors branch with solid Canvas background and CanvasText foreground. Avoid using opacity on text as a hierarchy mechanism in forced-colors. It may interact poorly with system colors. Use font-weight and size instead.
Status badges that communicate error, warning, and success through red, green, and yellow fills without text labels fail when colors normalize. Under forced-colors, give badges a Canvas background, CanvasText color, a CanvasText border, and a text label reading Error or Success. WCAG Use of Color already requires non-color cues. Forced-colors makes that requirement impossible to ignore.
Single-color icons should use currentColor so they track CanvasText or ButtonText as text color changes. Multi-color brand icons may need forced-color-adjust: none or simplified monochrome variants authored specifically for forced-colors media. Modal overlays should use solid system backgrounds and maintain focus trapping. Toast notifications need text labels and icons, not green-only bars.
Cooperating with the operating system
You cannot rely on DevTools simulation alone. Behavior differs by engine and OS version. On Windows 11, open Settings, then Accessibility, then Contrast themes, and choose Night Sky, Desert, or another built-in scheme. On Windows 10, use Settings, Ease of Access, High Contrast. Open your site in Edge and Firefox, both of which support forced-colors. Tab through interactive controls and verify focus, links, forms, and dialogs. Toggle themes and confirm layout does not depend on specific hex pairs.
Edge DevTools includes emulation for forced-colors in the Rendering pane. That is useful for quick checks during development, not a substitute for operating-system testing. Selenium and Playwright can emulate forced-colors in Chromium via emulatedMediaFeatures in some versions. Pair automation with at least one manual Windows pass before release because emulators miss platform-specific bugs.
Teams sometimes try forced-color-adjust: none on body to preserve brand across the entire page. Users who need High Contrast selected that mode deliberately. Stripping it is an accessibility regression and may violate organizational policy aligned with WCAG 2.2 Level AA procurement requirements. The professional approach runs three parallel experiences. The default experience ships full brand with contrast-checked pairs and prefers-contrast honored. The forced-colors experience keeps layout intact, preserves semantics, and embraces system colors. Meaning never relies on color alone because icons, text, and patterns survive palette stripping.
Ship a forced-colors stylesheet layer in your design system. Ten well-chosen rules that prevent the worst failures beat fifty per-component hacks discovered in QA the week before launch. Primary buttons get ButtonFace and ButtonText with a visible border. Links get LinkText and VisitedText with underline on hover. Cards get Canvas background and CanvasText borders. Inputs prefer native styling or Field and FieldText. Disabled states use GrayText without relying on low opacity alone. Selected table rows use Highlight and HighlightText.
Consider how forced-colors interacts with components you might not test in isolation. Data tables with zebra striping defined by two nearly identical grays collapse to a single Canvas background. The row distinction you relied on vanishes unless you add borders or text markers that do not depend on subtle fill differences. Navigation sidebars that used a darker navy to separate chrome from content lose that separation entirely. A one-pixel CanvasText border on the sidebar edge costs almost nothing in default mode and saves orientation in forced-colors. Dropdown menus styled with custom shadows and translucent overlays may render as opaque system surfaces. Ensure menu items still show Hover and Highlight states the platform provides rather than states you painted with rgba black at six percent opacity.
Print stylesheets and forced-colors rarely overlap in testing matrices, yet both strip decorative color. Authors who conflate the two sometimes copy print CSS into forced-colors branches and wonder why links disappear. Print CSS often forces black text for toner savings. Forced-colors preserves LinkText semantics because navigation remains navigation on screen. Write separate branches. Test them separately. The similarity is only superficial.
Documentation for your design system should include a forced-colors column alongside light and dark specifications. When a designer specs a success toast with a green left border four pixels wide, the engineering note should read: forced-colors uses CanvasText border plus the word Success in the message body. That single sentence prevents a sprint of rework when QA opens Night Sky theme on a loaner laptop.
Automated accessibility scanners catch some forced-colors problems indirectly by flagging color-only status indicators and missing focus styles. They do not replace OS testing because no CI job today reproduces every Windows contrast theme with full fidelity. Budget one afternoon per major release for manual High Contrast verification if your product sells into government, education, or healthcare segments where procurement questionnaires ask about Windows accessibility specifically.
The @media (forced-colors: active) query also pairs with the forced-colors: none branch for progressive enhancement. Components that already meet contrast requirements in default mode should not need heavy overrides. Components that used clever low-contrast grays for aesthetic minimalism need the most attention. Audit for box-shadow used as the only focus indicator, for placeholder text styled only with opacity, and for icon buttons with no accessible name when the icon was the only affordance painted in brand color.
forced-colors: active is not an edge case. It is a user holding a veto over your chroma choices, and that is how accessible platforms should work. Design for three modes: light, dark, and system-forced. Your sidebar navy was never guaranteed. CanvasText on Canvas was. When you write CSS that cooperates with the operating system instead of fighting it, High Contrast users get the predictability they asked for, and your layout survives the palette your brand team never approved. The teams that treat forced-colors as a first-class theme alongside light and dark ship fewer emergency hotfixes and fewer accessibility escalations after launch. The work is not glamorous. It is structural, like adding a third column to every component spec sheet, and it compounds into trust with users who have been burned by websites that pretended their brand mattered more than legibility.
SVG sprites and icon fonts need the same currentColor discipline as inline SVG. An icon font glyph hard-coded to #64748b in a private-use stylesheet rule will not track CanvasText when forced-colors activates. Refactor to inherited color or provide a forced-colors override that sets color on the icon element itself. Multi-stop gradients inside inline SVG charts should degrade to patterns or labeled segments rather than assuming hue survives.
Form validation is another trap. Error text styled only in #dc2626 without an icon or explicit Error prefix becomes indistinguishable from success messaging when both normalize to CanvasText. Mirror the badge guidance: text label, icon, border. Placeholder text that used opacity to appear subtle may become invisible when system Field and FieldText pair with different contrast than your rgba hack assumed. Test placeholder legibility in High Contrast White specifically because it is the harshest common theme for form chrome.
Finally, remember that forced-colors users are not asking you to redesign the product. They are asking you not to break it when the operating system rewrites your paint. Layout, copy, hierarchy, and keyboard paths should feel familiar. If your navigation collapses because active state was only a two-percent background tint difference, that is a design system gap forced-colors exposed, not a new requirement invented by Microsoft. Fix the hierarchy in default mode too and everyone benefits.