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

Esquema de color, contraste y transparencia

prefers-color-scheme con su compañera imprescindible color-scheme, los cuatro valores de prefers-contrast, y el estado honesto de prefers-reduced-transparency en 2026.

⏱ 16 min

Tres consultas de preferencia que la gente agrupa mentalmente y que tienen niveles de madurez muy distintos: prefers-color-scheme es universal desde 2019 y casi nadie la implementa entera, porque olvida la propiedad color-scheme que la acompaña; prefers-contrast es sólida y está infrautilizada; y prefers-reduced-transparency solo existe en Chromium, dato que conviene saber antes de construir nada encima.

🎯 Al terminar esta lección sabrás
  • Combinar prefers-color-scheme con la propiedad color-scheme y saber qué aporta cada una.
  • Interpretar los cuatro valores de prefers-contrast y qué ajuste del sistema activa cada uno.
  • Conocer el soporte real de prefers-reduced-transparency y decidir si lo usas.
  • Escribir un modo oscuro que no deje ni barras de desplazamiento ni controles a medias.

prefers-color-scheme y su mitad olvidada

La consulta refleja el ajuste de apariencia del sistema y tiene dos valores, light y dark. Está en todos los motores desde 2019 y su uso es directo:

:root { --fondo: oklch(0.99 0 0); --texto: oklch(0.2 0.02 260); }

@media (prefers-color-scheme: dark) {
  :root { --fondo: oklch(0.18 0.02 265); --texto: oklch(0.93 0.01 265); }
}

body { background: var(--fondo); color: var(--texto); }

Lo que casi todo el mundo olvida es que esto solo pinta tus elementos. Los controles de formulario nativos, las barras de desplazamiento, el color de fondo del lienzo antes de que cargue tu CSS y el resaltado de selección de texto los dibuja el navegador, y para que se enteren del cambio hay que declarar la propiedad color-scheme:

:root { color-scheme: light dark; }

Con esa línea, el navegador sabe que tu página funciona en los dos esquemas y adapta por su cuenta las barras de desplazamiento, los input nativos, los select, los checkbox y el fondo por defecto del documento. Sin ella, obtienes un modo oscuro con barras blancas y campos de formulario luminosos que delatan que el trabajo está a medias.

El valor light dark significa “admito los dos y prefiero el primero si el sistema no dice nada”. También se puede fijar a dark a secas en un elemento concreto para forzar controles oscuros en una zona, lo cual es útil en cabeceras oscuras dentro de una página clara.

💡
Evita el destello del fondo claro

El navegador pinta el fondo del documento antes de aplicar tu CSS. Para que ese primer pintado ya sea oscuro cuando toca, declara el esquema también en el marcado, que se procesa antes que la hoja de estilos: <meta name="color-scheme" content="light dark"> en el head. Es la diferencia entre un cambio de tema limpio y un destello blanco en cada carga.

prefers-contrast

Tiene cuatro valores: no-preference, more, less y custom. Refleja ajustes como “Aumentar contraste” en macOS o los temas de contraste de Windows, y llegó a Chrome 96, Firefox 101 y Safari 14.1, con una cobertura del 94% del tráfico global.

.tarjeta {
  border: 1px solid color-mix(in oklch, currentColor 18%, transparent);
  color: color-mix(in oklch, currentColor 75%, transparent);
}

@media (prefers-contrast: more) {
  .tarjeta {
    border-color: currentColor;
    color: currentColor;
  }
  .tarjeta .secundario { opacity: 1; }
}

El patrón que resuelve el 90% de los casos es exactamente ese: todo lo que estaba atenuado deja de estarlo. Los textos secundarios al 70% de opacidad, los bordes translúcidos, los iconos apagados y los estados deshabilitados con poco contraste son legibles para la mayoría y no para quien ha pedido más contraste.

less es el caso raro y casi nadie lo atiende: hay personas con sensibilidad a la luz o con ciertos tipos de migraña para quienes el contraste máximo resulta doloroso. Si tienes una paleta muy contrastada, suavizarla ahí es una mejora real.

custom indica que el usuario ha elegido una combinación específica de colores del sistema, y en la práctica suele venir acompañado de forced-colors, que tiene su propia lección.

prefers-reduced-transparency: el estado honesto

La consulta refleja ajustes como “Reducir transparencia” de macOS y Windows, pensados para quien tiene dificultad para leer texto sobre fondos translúcidos o difuminados.

El soporte en agosto de 2026 es este:

Motor Estado
Chrome y Edge desde la versión 118, octubre de 2023
Firefox implementado pero desactivado por defecto tras un flag
Safari no implementado, ni en escritorio ni en iOS

La cobertura ronda el 73% del tráfico global, y toda ella es Chromium. Dicho claramente: si tu interfaz depende de esta consulta para ser legible, es ilegible para todos los usuarios de Safari y de Firefox que hayan activado el ajuste del sistema, que es precisamente el grupo al que querías atender.

Eso no la hace inútil, la hace una mejora estrictamente progresiva:

.panel-vidrio {
  background: color-mix(in oklch, var(--fondo) 65%, transparent);
  backdrop-filter: blur(12px);
}

@media (prefers-reduced-transparency: reduce) {
  .panel-vidrio {
    background: var(--fondo);
    backdrop-filter: none;
  }
}

Y sugiere una conclusión más incómoda: si el contraste de tu panel translúcido depende de que el usuario pueda desactivar la transparencia, el diseño ya era frágil. Un fondo difuminado sobre contenido arbitrario no garantiza ningún ratio de contraste, y la solución robusta es una capa de color sólida por debajo del texto con contraste suficiente por sí misma, independientemente de lo que haya detrás.

Escribirlas juntas

Las tres consultas son independientes y se combinan sin problema. Un patrón que funciona bien es tener una capa de tokens y ajustarla en cada preferencia, en lugar de repetir reglas por componente:

:root {
  color-scheme: light dark;
  --fondo: oklch(0.99 0 0);
  --texto: oklch(0.2 0.02 260);
  --tenue: oklch(0.5 0.02 260);
  --borde: color-mix(in oklch, var(--texto) 18%, transparent);
  --velo: 0.65;
}

@media (prefers-color-scheme: dark) {
  :root { --fondo: oklch(0.18 0.02 265); --texto: oklch(0.93 0.01 265); --tenue: oklch(0.7 0.02 265); }
}

@media (prefers-contrast: more) {
  :root { --tenue: var(--texto); --borde: var(--texto); }
}

@media (prefers-reduced-transparency: reduce) {
  :root { --velo: 1; }
}

Con esta estructura, ningún componente conoce ninguna preferencia: todos leen tokens, y las preferencias se resuelven en un único sitio. Añadir una cuarta preferencia mañana es añadir un bloque más, no revisar cincuenta componentes.

Las preferencias del usuario son la única entrada que no puedes probar en tu máquina

Hay una asimetría práctica que explica por qué estas consultas están tan mal atendidas incluso en productos cuidados: el ancho de la ventana lo puedes cambiar arrastrando, el idioma lo puedes cambiar en un menú, pero las preferencias de accesibilidad están en el sistema operativo y casi nadie las toca durante el desarrollo. El resultado es que el código que las atiende se escribe sin verlo nunca funcionar, y el código que no se ve nunca es el código que está roto. La única defensa práctica es integrar la comprobación en el flujo de trabajo: los navegadores basados en Chromium tienen emulación de todas estas consultas en el panel de renderizado de sus herramientas, Firefox tiene la suya, y activarlas debería ser un paso más de la revisión de cualquier componente, igual que mirarlo en móvil. Y hay una implicación de diseño que va más allá de la herramienta: cada preferencia que atiendes multiplica por dos los estados visuales que tu sistema puede producir, y esa combinatoria solo es manejable si las preferencias se resuelven en una capa de tokens y no en cada componente. La arquitectura no es un lujo aquí; es lo que hace la diferencia entre atender tres preferencias y atender una.