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

La sintaxis de rango en media queries

Comparadores en lugar de prefijos: qué gana en legibilidad, el solapamiento de un píxel que elimina, y por qué los umbrales deben ir en rem y no en px.

⏱ 15 min

min-width y max-width fueron una convención impuesta por las limitaciones del analizador de CSS de 2001, no una decisión de diseño: no había forma de meter operadores de comparación en un preludio de at-rule sin ambigüedades. Media Queries nivel 4 arregló eso, y el resultado no es solo más corto de leer: elimina una clase entera de errores que producían dos reglas aplicándose a la vez en el valor frontera.

🎯 Al terminar esta lección sabrás
  • Traducir entre la sintaxis de prefijo y la de rango en los dos sentidos.
  • Escribir intervalos cerrados en una sola condición.
  • Eliminar el solapamiento del valor frontera con extremos abiertos.
  • Justificar el uso de rem en los umbrales frente a px.

Las dos sintaxis

/* prefijo, la de siempre */
@media (min-width: 40rem) { }
@media (max-width: 60rem) { }
@media (min-width: 40rem) and (max-width: 60rem) { }

/* rango, desde Media Queries nivel 4 */
@media (width >= 40rem) { }
@media (width <= 60rem) { }
@media (40rem <= width <= 60rem) { }

La traducción es mecánica: min- es >= y max- es <=. Los operadores disponibles son <, <=, >, >= y =, y las características que admiten rango son las que tienen un valor ordenable: width, height, aspect-ratio, resolution, color, monochrome y color-index.

El soporte llegó en Firefox 63, Chrome 104 y Safari 16.4, y ronda el 93% del tráfico global. Es sintaxis segura desde hace tiempo, y la misma que usa @container, con lo que unificar las dos formas de escribir condiciones tiene también un valor de coherencia.

El solapamiento que desaparece

El problema clásico de la sintaxis de prefijo aparece cuando encadenas breakpoints:

@media (max-width: 767px) { /* movil */ }
@media (min-width: 768px) { /* escritorio */ }

Se escribe con 767 y 768 precisamente para no solapar en el entero, y funciona mientras el ancho del viewport sea un número entero de píxeles CSS. Deja de serlo con el zoom del navegador, con pantallas de densidad fraccionaria y con ciertos factores de escala del sistema, donde el ancho puede valer 767.5. Con ese valor ninguna de las dos reglas se aplica, y obtienes un layout sin estilos de breakpoint que nadie ha diseñado.

Con extremos abiertos el problema no existe porque no hay hueco:

@media (width < 48rem) { /* movil */ }
@media (width >= 48rem) { /* escritorio */ }

Las dos condiciones particionan la recta real de forma exhaustiva y sin solapamiento, para cualquier valor, entero o no. Es la única forma de escribir breakpoints consecutivos que es correcta por construcción en lugar de correcta por convenio.

Para tramos intermedios, el intervalo semiabierto es igual de limpio:

@media (width < 30rem)             { }
@media (30rem <= width < 48rem)    { }
@media (48rem <= width < 64rem)    { }
@media (width >= 64rem)            { }
💡
La convención que evita el error

Adopta una regla de equipo y no la rompas: todos los umbrales inferiores cerrados y todos los superiores abiertos. Con esa convención, dos tramos consecutivos comparten el mismo número y es imposible dejar un hueco o crear un solapamiento. El número aparece dos veces, lo cual es feo, y a cambio la corrección es verificable de un vistazo.

Por qué rem y no px

Un umbral en px es un ancho de ventana. Un umbral en rem es un ancho de ventana medido en tamaños de fuente del usuario, y esa diferencia importa mucho más de lo que parece.

Quien tiene la fuente base configurada a 24px en lugar de 16px necesita el layout de una columna antes que quien la tiene por defecto, porque su texto ocupa un 50% más. Con 48rem, el umbral se mueve solo a 1152px en lugar de 768px y esa persona obtiene el layout apilado cuando lo necesita. Con 768px, no.

Un detalle técnico importante: dentro de una media query, rem y em se resuelven contra el tamaño de fuente inicial del navegador, no contra el font-size que hayas puesto en :root. Eso es exactamente lo que quieres —el umbral sigue la preferencia del usuario y no tus estilos—, pero significa que si has hecho el viejo truco de html { font-size: 62.5% } para que 1rem valga 10px, tus media queries no se ven afectadas por ese cambio y siguen midiendo en unidades de 16px. Es una fuente clásica de umbrales que no cuadran con el resto del CSS.

Combinaciones y negación

Las reglas de composición son las mismas que ya viste en @container:

@media (width >= 48rem) and (orientation: landscape) { }
@media (width < 30rem), (orientation: portrait) { }        /* la coma es un O */
@media not (width >= 48rem) { }

La coma sigue siendo el operador de disyunción histórico y funciona en todas partes. or dentro de los paréntesis existe en el nivel 4 y tiene soporte más reciente, así que la coma sigue siendo la opción segura para separar consultas independientes. Y not niega una condición completa; no se puede aplicar a una característica suelta dentro de una conjunción sin paréntesis.

Un valor que casi nunca sirve pero conviene conocer: @media (width = 48rem) comprueba la igualdad exacta. Con anchos fraccionarios es prácticamente imposible de cumplir, así que su utilidad real se limita a características discretas.

La sintaxis que elimina un estado inválido vale más que la que ahorra teclas

La sintaxis de rango se suele vender por concisión, y esa es su virtud menos interesante. Lo que de verdad aporta es que hace inexpresable un error concreto: con min-width y max-width es sintácticamente posible escribir dos breakpoints con un hueco entre ellos, y no hay nada en el lenguaje que te avise; con extremos abiertos compartiendo el mismo número, el hueco no se puede escribir sin que salte a la vista. Ese es el criterio con el que conviene juzgar cualquier mejora de sintaxis, en CSS y fuera de él: no cuántos caracteres ahorra, sino cuántos estados incorrectos deja de poder representar. Es la misma razón por la que un tipo suma es mejor que un booleano más un campo opcional, por la que una fecha con zona horaria explícita es mejor que una cadena, y por la que clamp() es mejor que tres reglas con media queries. Cuando evalúes una característica nueva del lenguaje, salta la parte de “qué permite hacer” y pregúntate qué error deja de ser posible; si la respuesta es ninguno, es azúcar, y el azúcar se adopta por gusto. Si la respuesta es alguno, es una mejora de corrección y se adopta por disciplina.