wandres.dev
CONTAINER QUERIES · Consultar el contenedor, no la ventana

La sintaxis de @container

Las características consultables, la sintaxis de rango, las combinaciones booleanas, el anidamiento con media queries y las dos unidades que no puedes usar dentro de la condición.

⏱ 18 min

La sintaxis de @container es deliberadamente parecida a la de @media, y esa familiaridad ahorra aprender un lenguaje nuevo a costa de esconder tres diferencias que importan: qué se puede consultar, contra qué se resuelven las unidades, y qué ocurre cuando no hay ningún contenedor que responda. Esta lección es el manual de referencia del que vas a tirar cada vez que escribas una consulta.

🎯 Al terminar esta lección sabrás
  • Consultar las seis características de tamaño disponibles y saber cuál exige container-type: size.
  • Escribir condiciones con sintaxis de rango y combinarlas con and, or y not.
  • Anidar @container con @media y con otra @container sabiendo qué significa cada anidamiento.
  • Predecir el resultado de una consulta cuando no existe ningún contenedor válido.

Qué se puede consultar

Las características de tamaño disponibles son seis, y se dividen en las que funcionan con inline-size y las que exigen size.

Característica Qué mide Exige
inline-size tamaño en el eje en línea inline-size o size
width ancho físico inline-size o size en escritura horizontal
block-size tamaño en el eje de bloque size
height alto físico size
aspect-ratio proporción entre ancho y alto size
orientation portrait o landscape size

Las cuatro últimas necesitan conocer las dos dimensiones, y por eso obligan a container-type: size con todo lo que eso implica. Si escribes una consulta de altura contra un contenedor declarado como inline-size, la consulta no falla ruidosamente: simplemente nunca se cumple, porque ese contenedor no es un contenedor válido para esa característica y el motor sigue buscando hacia arriba.

Prefiere las características lógicas (inline-size, block-size) sobre las físicas por la misma razón que en el resto del CSS moderno: siguen significando lo que quieres decir cuando cambia el modo de escritura.

La sintaxis de la condición

Rango y prefijo

Las dos formas son válidas y equivalentes:

@container (min-inline-size: 40rem) { /* estilo antiguo */ }
@container (inline-size >= 40rem)   { /* estilo de rango, preferible */ }

La de rango admite además intervalos cerrados en una sola condición, que con prefijos exigían dos:

@container (30rem <= inline-size < 48rem) {
  .tarjeta { grid-template-columns: 1fr; }
}

Los operadores disponibles son <, <=, >, >= y =. Fíjate en la ventaja del intervalo cerrado con extremo abierto: usar < en el extremo superior elimina el solapamiento de un píxel que producían max-width y min-width consecutivos, un fallo clásico que en pantallas de alta densidad producía dos reglas aplicándose a la vez en el valor frontera.

Combinaciones booleanas

Puedes combinar condiciones con and, or y not, con paréntesis cuando haga falta desambiguar:

@container (inline-size > 40rem) and (aspect-ratio > 1) {
  .media { grid-template-columns: 2fr 1fr; }
}

@container not (inline-size > 40rem) {
  .media { grid-template-columns: 1fr; }
}

@container (inline-size < 20rem) or (block-size < 12rem) {
  .media .detalle { display: none; }
}

Igual que en las media queries, no puedes mezclar and y or en el mismo nivel sin agrupar con paréntesis, y not se aplica a una condición completa. La restricción existe para evitar ambigüedades de precedencia que sería facilísimo leer al revés.

Unidades dentro de la condición

Aquí está la diferencia menos conocida con @media, y es una prohibición explícita: las unidades relativas al contenedor no se pueden usar en la condición de una container query. Escribir @container (inline-size > 50cqi) sería preguntar si el contenedor es mayor que la mitad de sí mismo, una circularidad tan evidente que la especificación la prohíbe de entrada. Una consulta que las use es inválida y se descarta entera.

Para el resto de unidades, la recomendación práctica es usar rem, que se resuelve siempre contra el tamaño de fuente de la raíz y por tanto es predecible y respeta las preferencias de tamaño de letra del usuario. Con px obtienes umbrales que no se mueven cuando alguien amplía la tipografía, que casi nunca es lo que quieres.

💡
Qué pasa si no hay contenedor

Si ninguna ancestro es un contenedor válido para la consulta, la condición se evalúa como falsa y los estilos no se aplican. Nunca hay error ni aviso. Esto convierte al @container en seguro por omisión —lo peor que pasa es que no se aplique la mejora— y a la vez en silencioso cuando te has olvidado del container-type. Si una consulta “no funciona”, lo primero que hay que comprobar es que exista el contenedor y que su tipo cubra la característica consultada.

Anidar consultas

@container se puede anidar dentro de otra @container, dentro de @media, dentro de @supports y dentro de reglas con anidamiento nativo. Cada anidamiento significa una conjunción.

/* solo cuando el contenedor es ancho Y la ventana no es de impresion */
@media screen {
  @container (inline-size > 40rem) {
    .tarjeta { grid-template-columns: 12rem 1fr; }
  }
}

/* dos escalas de contenedor: la tarjeta dentro del tablero */
@container tablero (inline-size > 60rem) {
  @container tarjeta (inline-size > 24rem) {
    .tarjeta .resumen { display: block; }
  }
}

El segundo ejemplo es imposible de expresar con media queries y muestra por qué esto es un cambio cualitativo: estás condicionando a dos escalas independientes del layout a la vez, sin que ninguna de las dos sea global.

Con anidamiento nativo, la forma habitual de escribir un componente queda muy compacta:

.tarjeta {
  display: grid;
  gap: 0.75rem;

  @container celda (inline-size > 26rem) {
    grid-template-columns: 12rem 1fr;
    align-items: start;
  }
}

Qué se puede cambiar y qué no

Dentro de un @container puedes escribir cualquier declaración que afecte a descendientes del contenedor. Lo que no puedes es cambiar propiedades del propio contenedor que afecten a su tamaño en el eje consultado, porque sencillamente el contenedor no es seleccionable desde dentro de su propia consulta. Esa es la restricción central del modelo y merece la lección siguiente entera.

Tampoco puedes usar @container para cambiar el container-type o el container-name de nada relevante a la propia consulta: aunque la declaración se acepte, estarías construyendo la misma circularidad por otra vía y los motores la cortan.

La sintaxis se parece a @media a propósito, y esa familiaridad tiene un coste

Copiar la sintaxis de las media queries fue una decisión de diseño excelente para la adopción: nadie tuvo que aprender un lenguaje nuevo, las herramientas existentes casi funcionaron sin cambios y la migración mental fue de minutos. Pero la familiaridad transporta hábitos junto con la sintaxis, y aquí hay un hábito concreto que hace daño: en @media estás acostumbrado a que la condición sea siempre evaluable, porque el viewport siempre existe y siempre tiene un ancho. En @container la condición puede ser inevaluable, y el resultado de lo inevaluable es “falso”, indistinguible de “la condición no se cumple”. Esa asimetría es la fuente de casi todos los ratos perdidos con esta característica: pasas veinte minutos ajustando el umbral de una consulta que jamás se iba a evaluar porque olvidaste el container-type tres niveles más arriba. La disciplina que lo evita es de dos segundos y conviene automatizarla: antes de tocar un umbral, verifica en el inspector que el elemento tiene un contenedor de consultas asignado. Los navegadores modernos lo muestran, y esa comprobación distingue “la condición es falsa” de “no había pregunta que responder”.

⚔️ Ejercita la sintaxis
  1. Escribe la misma condición en sintaxis de prefijo y de rango y comprueba que producen resultados idénticos.
  2. Consulta block-size sobre un contenedor inline-size y observa que no ocurre nada; corrígelo.
  3. Monta un intervalo cerrado con < en el extremo superior y comprueba en el valor frontera exacto que solo se aplica una de las dos reglas.
  4. Anida una @container de tarjeta dentro de una @container de tablero y verifica que hacen falta las dos condiciones.
  5. Escribe deliberadamente @container (inline-size > 50cqi) y comprueba en el inspector que la regla se descarta.