Forced colors mode lets a browser replace authored colors with a small user-selected palette, most often for high-contrast accessibility settings. Supporting it with CSS keeps content, controls, focus rings, and selected states visible when decorative color choices are no longer in charge.
The safest forced-colors branch is small. Let the browser force ordinary text and form-control colors, and use system color keywords such as Canvas, CanvasText, ButtonFace, ButtonText, Highlight, and HighlightText where custom CSS draws borders, indicators, or interaction states.
Focus indicators and selected states need visible outlines, borders, or markers because shadows and subtle background tints can disappear under forced colors. Avoid forced-color-adjust: none except for rare brand or data-visualization elements that must keep authored colors, and test with browser emulation before relying on the result.
Related: How to support system dark mode with CSS
Related: How to style keyboard focus with CSS
Related: How to style an HTML button with CSS
<section class="panel"> <h1>Account plan</h1> <p>Choose the plan before saving the account settings.</p> <button class="action" type="button">Save changes</button> <span class="status" aria-current="true">Selected plan</span> </section>
Use real text, attributes, and state classes for meaning. A green background or a blue shadow alone does not tell a forced-colors user what changed.
:root { color-scheme: light dark; --page: #f8fafc; --surface: #ffffff; --text: #111827; --muted: #475569; --accent: #2563eb; --accent-text: #ffffff; --border: #cbd5e1; --success: #166534; --success-bg: #dcfce7; } body { background: var(--page); color: var(--text); } .panel { background: var(--surface); border: 1px solid var(--border); box-shadow: 0 20px 48px rgb(15 23 42 / 18%); } .panel p { color: var(--muted); } .action { background: var(--accent); color: var(--accent-text); border: 2px solid transparent; box-shadow: 0 8px 20px rgb(37 99 235 / 22%); } .action:focus-visible { outline: 3px solid #f97316; outline-offset: 3px; } .status { border: 1px solid #86efac; background: var(--success-bg); color: var(--success); }
The transparent button border reserves space so the border can become visible in forced colors without changing the button size.
@media (forced-colors: active) { .panel { border-color: CanvasText; box-shadow: none; } .action { background: ButtonFace; color: ButtonText; border-color: ButtonText; box-shadow: none; } .action:hover { border-color: Highlight; } .action:focus-visible { outline: 3px solid Highlight; outline-offset: 3px; } .status { background: Canvas; color: CanvasText; border-color: Highlight; } }
Canvas and CanvasText follow the page surface and text colors. Highlight and HighlightText follow the user's selected-item palette.
.status { display: inline-flex; align-items: center; gap: 0.5rem; } .status::before { content: ""; width: 0.75rem; height: 0.75rem; border: 2px solid currentColor; border-radius: 50%; background: #22c55e; box-shadow: 0 0 0 4px rgb(34 197 94 / 18%); } @media (forced-colors: active) { .status::before { background: Highlight; border-color: Highlight; box-shadow: none; } }
Do not rely on box-shadow for the only focus, badge, or selected-state signal. Forced colors can suppress shadows during painting.
Open DevTools → Rendering → Emulate CSS media feature forced-colors, then choose forced-colors: active. Windows High Contrast mode should exercise the same media query in normal browsing.
matchMedia("(forced-colors: active)").matches true
document.querySelector(".action").focus() getComputedStyle(document.querySelector(".action")).outlineStyle "solid" getComputedStyle(document.querySelector(".action")).outlineWidth "3px" getComputedStyle(document.querySelector(".action")).boxShadow "none"
getComputedStyle(document.querySelector(".status")).borderColor "rgba(26, 235, 255, 0.8)" getComputedStyle(document.querySelector(".status")).backgroundColor "rgb(0, 0, 0)" getComputedStyle(document.querySelector(".status")).color "rgb(255, 255, 255)"
The exact RGB values depend on the active forced-colors palette. The border should not be transparent, and the text should stay on a matching system surface.