Skip to content

Forced colors / high contrast

themes specs/themes/forced-colors.kmd

How Koder UIs respond when the OS forces a high-contrast color palette (Windows High Contrast / `forced-colors: active`, and equivalent platform modes): defer to the user's system colors, keep every shape and focus ring visible via borders + system-color keywords, and degrade the Verge token layer gracefully. The OS-driven counterpart to the opt-in low-vision mode in a11y-modes.kmd. Fluent 2 + WCAG parity.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Forced colors / high contrast

Status: v0.1.0 — Draft. Promoted from the Microsoft Fluent 2 parity scan (meta/docs/stack #081); fills the "Windows high-contrast bridge / forced-colors (separate spec)" deferred by specs/themes/a11y-modes.kmd. Sources: CSS forced-colors media feature; Windows High Contrast; Fluent 2 high-contrast guidance.

R1 — Two distinct things

  • Forced colors (this spec, OS-driven): the OS replaces the palette (Windows High Contrast, forced-colors: active). Koder does NOT pick the colors — it defers to the user's system palette.
  • Low-vision mode (a11y-modes.kmd, Koder opt-in): a Koder-chosen AAA-contrast theme the user enables in Settings. Independent; both can be active.

Detection is via the forced-colors: active media query (and the platform equivalent on Flutter — highContrast/accessibility flags).

R2 — Defer to system colors

Under forced colors, map UI to the system color keywords rather than brand tokens: Canvas/CanvasText (surface/text), LinkText, ButtonFace/ButtonText, Highlight/HighlightText (selection), GrayText (disabled), Field/FieldText. Let the OS resolve them; do not hard-code hex in this mode.

R3 — Shapes survive (borders, not fills)

  • Decorative backgrounds, shadows, and tonal elevation are typically flattened by the OS — so components MUST stay legible by border / outline, not fill alone. Add a 1px system-color border to surfaces that relied on background contrast (cards, inputs, buttons, menus).
  • Never forced-color-adjust: none to "preserve branding" except for genuine content (e.g. a photo, a brand logo swatch) — never for chrome.

R4 — Focus and state always visible

  • The focus ring uses a system color (Highlight) and stays visible (it is the primary navigation cue when fills vanish).
  • Selected / checked / disabled states are conveyed by system-color + shape/icon, never by a brand fill that the OS will strip (pairs with the color-is-not-the-only-signal rule, WCAG 1.4.1).

R5 — Icons & images

  • Symbolic UI icons (specs/icons/ui-symbols.kmd) use currentColor so they inherit the system text color and remain visible.
  • Purely decorative images may be hidden under forced colors; meaningful images keep a text alternative.

R6 — Token-layer degradation

The Verge token layer (specs/themes/verge.kmd) defines forced-colors fallbacks centrally so components inherit correct behavior without per-component overrides; light/dark (specs/themes/light-dark.kmd) is orthogonal (forced colors overrides both). Verification ties into the BR conformance matrix (specs/accessibility/conformance-br.kmd).

Não-escopo

  • The opt-in low-vision / color-blind modes (specs/themes/a11y-modes.kmd).
  • The base light/dark contract (specs/themes/light-dark.kmd).
  • Per-component visual specs (each owns its forced-colors border per R3).

References