wandres.dev
MEDIA QUERIES MODERNAS · Rangos y consultas de usuario

forced-colors: cuando el sistema decide los colores

Qué hace el modo de colores forzados con tu paleta, qué se rompe siempre, los colores del sistema que debes usar, y por qué forced-color-adjust: none casi nunca es la respuesta.

⏱ 17 min

En el modo de colores forzados, el navegador sustituye tu paleta entera por una del sistema elegida por el usuario. No es un modo oscuro ni un tema de alto contraste que tú controlas: es una anulación agresiva que ignora casi todos tus colores para garantizar un contraste que tú no puedes estropear. Lo usa gente con baja visión que depende de ello para poder leer, y tu trabajo no es diseñar para él sino asegurarte de que tu interfaz sigue siendo comprensible cuando desaparece el color.

🎯 Al terminar esta lección sabrás
  • Explicar qué propiedades anula el modo de colores forzados y cuáles respeta.
  • Reconocer los cuatro patrones de diseño que se rompen sistemáticamente.
  • Usar las palabras clave de color del sistema para reparar lo que se rompe.
  • Justificar cuándo forced-color-adjust: none es legítimo y cuándo es un abuso.

Qué hace exactamente

Cuando el usuario activa un tema de contraste en Windows —o el modo equivalente en otras plataformas— el navegador entra en modo de colores forzados y anula color, background-color, border-color, box-shadow, text-shadow, column-rule-color, outline-color y los colores de relleno y trazo de SVG, sustituyéndolos por un pequeño conjunto de colores del sistema. También convierte las imágenes de fondo en nada cuando actúan como decoración.

La consulta para detectarlo tiene dos valores:

@media (forced-colors: active) { /* el sistema manda */ }
@media (forced-colors: none)   { /* lo normal */ }

El soporte es amplio: Edge desde la 79, Chrome desde la 89, Firefox desde la 89 y Safari desde la 16, con un 94% del tráfico global. Otra cosa es que el modo exista en la plataforma: Windows tiene temas de contraste, y en macOS e iOS no hay un modo equivalente, así que allí forced-colors no se activa nunca y la preferencia de accesibilidad que sí existe se expresa con prefers-contrast. Atender solo a una de las dos deja fuera a media población.

Lo que se rompe y cómo repararlo

Los cuatro patrones que fallan

Cuatro patrones fallan de forma sistemática y merece la pena reconocerlos.

Bordes hechos con sombra. box-shadow: 0 0 0 1px es un truco habitual para dibujar un borde sin afectar al layout. En modo forzado la sombra desaparece y el elemento se queda sin límite visible. Usa border u outline, que sí se conservan con un color del sistema.

Información transmitida solo por color de fondo. Una fila verde que significa “correcto” y otra roja que significa “error” se convierten en dos filas idénticas. La solución no es específica de este modo: es la regla de accesibilidad de no codificar información solo con color, y aquí simplemente se hace visible.

Iconos SVG con relleno fijo. Un SVG con fill="#3b82f6" incrustado en el atributo mantiene su color, y con suerte el resultado es un icono azul invisible sobre el fondo del sistema. Pinta los iconos con fill="currentColor" y deja que hereden.

Estados marcados con opacidad o con un tono más claro. Un botón deshabilitado al 50% de opacidad sigue al 50% —la opacidad no se anula— pero el color que atenúa ya no es el tuyo. Para estados hay un color del sistema específico, GrayText, que es lo correcto.

🛑
El foco es lo más grave

Un anillo de foco hecho con box-shadow desaparece por completo en modo forzado, y con él la única pista de dónde está el teclado. Si usas sombra para el foco, añade un outline transparente que el modo forzado convertirá en visible: outline: 2px solid transparent; outline-offset: 2px;. Es el patrón estándar y cuesta dos líneas.

Los colores del sistema

CSS define un conjunto de palabras clave que se resuelven a los colores del tema activo. Las que más vas a usar:

Palabra clave Uso
Canvas fondo del contenido
CanvasText texto sobre ese fondo
LinkText enlaces sin visitar
VisitedText enlaces visitados
ButtonFace fondo de los controles
ButtonText texto y bordes de los controles
Field fondo de los campos de entrada
FieldText texto de los campos
Highlight fondo de la selección y del estado activo
HighlightText texto sobre Highlight
GrayText contenido deshabilitado

Se usan como cualquier color y solo dentro del bloque de la consulta, porque fuera de ella se resuelven a los colores por defecto del sistema y no a los del tema:

@media (forced-colors: active) {
  .tarjeta { border: 1px solid CanvasText; background: Canvas; color: CanvasText; }
  .tarjeta__accion { background: ButtonFace; color: ButtonText; border: 1px solid ButtonText; }
  .tarjeta__accion[disabled] { color: GrayText; border-color: GrayText; }
  .tarjeta.seleccionada { background: Highlight; color: HighlightText; }
}

La combinación de fondo y texto siempre debe salir del mismo par —Canvas con CanvasText, ButtonFace con ButtonText, Highlight con HighlightText— porque el sistema garantiza el contraste dentro del par y no entre parejas distintas.

forced-color-adjust

forced-color-adjust: none desactiva la anulación en un elemento y sus descendientes. Es una escotilla de escape y hay que tratarla como tal.

Es legítima cuando el color es la información y no un adorno: una muestra de color en un selector, la leyenda de un gráfico donde las series se distinguen por color, la previsualización de un tema, un logotipo cuya identidad depende de sus colores exactos.

Es un abuso cuando se usa para conservar el diseño. Si la aplicas a un contenedor grande porque en modo forzado “se ve feo”, le has quitado a alguien la herramienta que necesitaba para leer tu página, y lo has hecho por motivos estéticos.

/* legitimo: la muestra ES el dato */
.muestra-color { forced-color-adjust: none; }

/* y aun asi, dale un borde que sobreviva */
@media (forced-colors: active) {
  .muestra-color { outline: 1px solid CanvasText; outline-offset: 1px; }
}

Existe además forced-color-adjust: preserve-parent-color, que conserva el color heredado del padre en lugar de forzarlo, pensado sobre todo para SVG donde el trazo debe seguir a currentColor.

Cómo probarlo

No hace falta un Windows con un tema de contraste activado, aunque es la prueba definitiva. Las herramientas de desarrollo de los navegadores basados en Chromium tienen una emulación de forced-colors en el panel de renderizado, y Firefox permite forzar los colores desde su configuración de accesibilidad. Con eso detectas los cuatro fallos de la sección anterior en un par de minutos por componente.

La comprobación que más valor da es sencilla: abre tu interfaz en modo forzado y pregúntate si podrías usarla sin haberla visto antes en color. Si la respuesta es no, el problema no está en el modo forzado; está en que tu interfaz codifica información en el color.

El modo forzado es un test de si tu interfaz depende del color para significar

Lo interesante de este modo no es la lista de propiedades que anula: es lo que revela. Una interfaz que sobrevive bien a los colores forzados es, casi siempre, una interfaz que ya cumplía tres cosas por separado —jerarquía construida con tamaño y espacio en vez de con color, estados marcados con más de un canal, y límites de las cajas dibujados con bordes reales—. Una que se desmorona es una que había estado usando el color como muleta para tres trabajos distintos a la vez, y que por tanto también falla, más silenciosamente, con daltonismo, con una pantalla mal calibrada, bajo el sol directo o impresa en blanco y negro. Por eso merece la pena verlo como lo que es: no un modo de accesibilidad más al que hay que dar soporte, sino el diagnóstico más barato que existe sobre la robustez de tu lenguaje visual. Tarda dos minutos, no requiere ninguna herramienta especial, y lo que descubre casi nunca se arregla dentro del bloque de la media query: se arregla en el diseño.