High contrast & forced-colors mode
High contrast & forced-colors mode
8 reglasTrata forced-colors como una intervención del OS, no como un tema CSS
Cuando el usuario activa el modo de colores forzados (Windows High Contrast y equivalentes), el sistema operativo instruye al navegador a reemplazar la paleta de la página por un set limitado elegido por el usuario. No es un dark mode ni un tema: es una intervención a nivel OS que anula color, background-color, border-color, box-shadow, text-shadow y SVG fill/stroke. El objetivo del usuario puede ser legibilidad, menos ruido visual, mitigar migranas o sensibilidad a la luz, no necesariamente "más contraste" matematico. La respuesta correcta es dejar que el OS aplique su paleta y solo intervenir donde la conversión automática rompe la UX.
Usa @media (forced-colors: active) para ajustar, no para pelear contra el modo
La media query forced-colors: active es para retoques quirurgicos donde la conversión automática produce resultados deficientes, no para reimplementar todo el diseño. La plataforma ya garantiza la legibilidad base; el autor solo compensa los casos puntuales: un box-shadow perdido que deja un botón sin forma, o un gradiente eliminado que borra información de estado. Escribir un stylesheet paralelo completo para forced-colors es antipatron: duplica mantenimiento y puede contradecir las preferencias del usuario. Microsoft documenta que la mayoría de los sitios necesitan muy pocas reglas CSS para este modo.
Usa system color keywords, no hex, para colores semánticos en forced-colors
Dentro de un bloque @media (forced-colors: active), los valores hex o RGB fijos pueden ser reemplazados de nuevo por el navegador, o no armonizar con la paleta del usuario. Los CSS system color keywords representan roles semánticos en la paleta del sistema y son la forma correcta de referirse a colores aquí: Canvas (fondo del documento), CanvasText (texto principal), ButtonFace/ButtonText (controles), Field/FieldText (inputs), Highlight/HighlightText (selección), LinkText, VisitedText, GrayText (deshabilitado), ButtonBorder, AccentColor/AccentColorText. Son 14 keywords vigentes que mapean a la paleta real.
color: #ffffff;
Hex fijo dentro de forced-colors: el sistema lo ignora o lo reemplaza.
Los bordes definen la forma cuando el fondo desaparece
En forced-colors mode, background-color es reemplazado por la paleta del sistema y box-shadow es forzado a none. Un botón o card que dependa unicamente de su color de fondo para comunicar forma y límites quedara invisible o indiferenciable del Canvas. El border y el outline CSS siguen procesandose (con el color del sistema), lo que los vuelve los principales portadores de forma en este modo. La técnica es declarar siempre un borde, incluso transparente en modo normal: el OS le aplica su color y el elemento conserva forma. WCAG 1.4.11 exige 3:1 mínimo para la información visual que identifica controles.
Los iconos SVG deben usar currentColor para heredar el color del sistema
En forced-colors mode, los atributos SVG fill y stroke con valores de color fijos son reemplazados por el sistema y pueden quedar ilegibles. currentColor es un valor CSS especial que hereda el color del elemento padre, y ese color si lo gestiona correctamente el modo forced-colors. Usar currentColor en iconos SVG evita colores incorrectos y elimina la necesidad de reglas específicas en @media (forced-colors: active): el icono se adapta automáticamente a light, dark y forced-colors con un solo valor. Coloca el SVG dentro de un elemento con color definido.
No uses background-image para información esencial: desaparece en forced-colors
En forced-colors mode, los background-image que no son url() (gradientes, patrones CSS) son forzados a none, e incluso los url() pueden quedar invisibles si el Canvas cambia a un tono que los neutraliza. Cualquier información esencial que dependa exclusivamente de un background-image, marcas en checkboxes custom, iconos de estado, badges de color, progreso por gradiente, desaparece para usuarios de High Contrast. La solución es codificar el significado con elementos reales: un SVG inline o un ::before con content y un color del sistema, no un fondo decorativo.
El foco debe ser visible en forced-colors sin depender de box-shadow
box-shadow es forzado a none en forced-colors mode, así que cualquier indicador de foco implementado solo con box-shadow, patrón común en design systems, desaparece justo para el grupo que más depende de un foco claro. La solución es declarar también un outline de color transparente en el estilo base: en modo normal se ve el box-shadow; en forced-colors el outline transparente recibe el color del sistema (tipicamente Highlight) y se vuelve visible automáticamente. WCAG 2.4.11 (Focus Appearance, AA en 2.2) exige área de al menos el perimetro x 2 CSS px y contraste 3:1.
prefers-contrast y forced-colors son distintos: responde a ambos por separado
prefers-contrast: more indica que el usuario prefiere mayor contraste sin necesariamente reemplazar colores: es una intención declarada dentro de tu paleta. forced-colors: active es una intervención real del OS que ya sustituyo los colores. El valor prefers-contrast: custom existe para el caso de paleta personalizada y se alinea con forced-colors: active. Responder solo a prefers-contrast: more no cubre a usuarios de High Contrast; responder solo a forced-colors no mejora la experiencia de quien quiere más contraste sin forzar colores. Cubrir ambos es valido y no son exclusivos.
Asume que cubre a todos. Los usuarios de High Contrast (paleta forzada por el OS) quedan sin atender porque su señal real es forced-colors.
- R-229 Trata forced-colors como una intervención del OS, no como un tema CSS
- R-230 Usa @media (forced-colors: active) para ajustar, no para pelear contra el modo
- R-231 Usa system color keywords, no hex, para colores semánticos en forced-colors
- R-232 Los bordes definen la forma cuando el fondo desaparece
- R-233 Los iconos SVG deben usar currentColor para heredar el color del sistema
- R-234 No uses background-image para información esencial: desaparece en forced-colors
- R-235 El foco debe ser visible en forced-colors sin depender de box-shadow
- R-236 prefers-contrast y forced-colors son distintos: responde a ambos por separado