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

container-type y container-name: declarar el contenedor

Los cuatro valores de container-type y lo que cuesta cada uno, cómo y cuándo nombrar un contenedor, la taquigrafía container y el estado real de scroll-state en 2026.

⏱ 17 min

Declarar un contenedor de consultas son dos propiedades y una taquigrafía, pero cada valor firma un contrato distinto con el motor y cada uno se cobra algo distinto. Elegir mal entre size e inline-size produce contenedores de altura cero; olvidar nombrar produce consultas que se resuelven contra el ancestro equivocado en cuanto alguien anida dos componentes. Media hora entendiendo estas dos propiedades ahorra tardes enteras de depuración.

🎯 Al terminar esta lección sabrás
  • Elegir el valor de container-type correcto para un componente dado y justificarlo.
  • Nombrar contenedores con criterio y evitar las colisiones de nombres.
  • Escribir la taquigrafía container con su orden y su separador.
  • Conocer el estado de soporte de scroll-state y decidir si lo usas.

Los cuatro valores de container-type

normal es el valor inicial de todos los elementos. Significa que el elemento no es contenedor para consultas de tamaño, pero sí lo es para consultas de estilo. No aplica ningún containment y no cuesta nada. Sirve además para revertir una declaración heredada de una hoja de librería.

inline-size hace del elemento un contenedor consultable en el eje en línea. Aplica containment de tamaño en ese eje y containment de style, y establece un contexto de formato independiente. Lo que no aplica, desde Chrome 129, Firefox 133 y Safari 18.4, es containment de layout: el contenedor ya no captura a los descendientes fixed ni crea contexto de apilamiento. Es el valor que vas a escribir el 95% de las veces.

size lo hace consultable en los dos ejes y aplica containment de tamaño completo. Solo tiene sentido si la altura del elemento viene determinada desde fuera; en cualquier otro caso el elemento colapsa a cero de alto.

scroll-state convierte al elemento en contenedor para consultas sobre su estado de desplazamiento: si está pegado por position: sticky, si su contenedor de scroll se puede desplazar en cierta dirección, o si es el objetivo de un snap. No aplica containment de tamaño.

.componente  { container-type: inline-size; }         /* el caso normal */
.celda-fija  { block-size: 18rem; container-type: size; }
.reset       { container-type: normal; }              /* deshacer una declaracion */
⚠️
scroll-state es solo Chromium en 2026

container-type: scroll-state y las consultas @container scroll-state(...) están implementadas en Chrome y Edge desde la versión 133, y no están en Firefox ni en Safari en agosto de 2026. Es una función de mejora progresiva legítima —la cabecera que cambia de aspecto al quedarse pegada, por ejemplo— pero no puedes construir sobre ella nada cuya ausencia rompa la interfaz. Si la usas, comprueba antes cómo se ve sin ella, porque en dos de los cuatro motores es exactamente lo que van a ver.

Nombrar contenedores

container-name asigna uno o varios identificadores a un contenedor. Una consulta sin nombre se resuelve contra el ancestro más cercano que sea contenedor válido para ese tipo de consulta; una consulta con nombre, contra el ancestro más cercano que además lleve ese nombre.

.tablero { container-type: inline-size; container-name: tablero; }
.tarjeta { container-type: inline-size; container-name: tarjeta; }

/* se resuelve contra .tarjeta si esta dentro de una, o contra .tablero si no */
@container (inline-size > 30rem) { .titulo { font-size: 1.5rem; } }

/* se resuelve siempre contra el tablero, aunque haya una tarjeta en medio */
@container tablero (inline-size > 60rem) { .titulo { letter-spacing: -0.02em; } }

La regla práctica es simple: nombra en cuanto puedas anidar. Mientras un componente sea hoja, la consulta sin nombre acierta siempre. En el momento en que una tarjeta pueda contener otra tarjeta, o en que un componente genérico pueda colocarse dentro de otro contenedor declarado por otra persona, la consulta sin nombre empieza a resolverse contra lo que toque, y el fallo es silencioso: no hay error, simplemente los estilos se aplican en el momento equivocado.

Un elemento puede llevar varios nombres, separados por espacios, lo que sirve para que responda tanto a consultas genéricas como específicas:

.panel { container-type: inline-size; container-name: panel superficie; }

Los nombres son identificadores globales, sin ámbito. No hay mecanismo de encapsulación, así que la única defensa contra colisiones es una convención: prefija con el nombre del componente o del paquete si publicas CSS que otros van a consumir.

La taquigrafía

container combina las dos propiedades con una barra como separador, y el orden es nombre primero, tipo después:

.tarjeta { container: tarjeta / inline-size; }

/* equivale exactamente a */
.tarjeta { container-name: tarjeta; container-type: inline-size; }

/* solo el nombre, tipo normal */
.seccion { container: seccion; }

Como toda taquigrafía, restablece a su valor inicial la parte que omitas. container: tarjeta pone container-type: normal, que es justo lo contrario de lo que quiere quien lo escribe deprisa. Si vas a usar la taquigrafía, escribe siempre las dos partes.

Dónde declararlo

La decisión de qué elemento es el contenedor es de arquitectura y determina qué significan tus consultas.

<ul class="galeria">
  <li class="celda"><article class="tarjeta">...</article></li>
</ul>

Si declaras el contenedor en .galeria, todas las tarjetas consultan el ancho de la galería entera: es lo correcto si quieres que todas cambien a la vez. Si lo declaras en .celda, cada tarjeta consulta el ancho de su celda: es lo correcto si las celdas pueden tener anchos distintos, que es lo que pasa en cuanto una tarjeta ocupa dos columnas.

.galeria {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 1rem;
}
.celda { container: celda / inline-size; }
.celda.destacada { grid-column: span 2; }   /* esta consultara un ancho mayor */

@container celda (inline-size > 26rem) {
  .tarjeta { display: grid; grid-template-columns: 12rem 1fr; gap: 1rem; }
}

Ese es el patrón que hace que una tarjeta destacada adopte automáticamente su disposición horizontal sin ninguna clase modificadora: el layout de la galería decide cuánto espacio le da, y la tarjeta reacciona a lo que le han dado.

Qué cuesta de verdad

Hay un miedo extendido a declarar contenedores por rendimiento y está mal calibrado. El containment que aplica container-type suele abaratar el layout, porque acota el subárbol que hay que recalcular; ese era su propósito original. Lo que sí tiene coste es la resolución de las consultas: por cada regla @container, el motor tiene que encontrar el contenedor correspondiente subiendo por el árbol, y hacerlo sobre miles de elementos con muchas reglas no es gratis.

La consecuencia práctica no es “declara pocos contenedores”, es declara los contenedores que alguna consulta usa. Un container-type sin ninguna @container que lo consulte solo te ha traído los efectos secundarios del containment sin ninguna funcionalidad a cambio.

Nombrar es lo único que te protege del acoplamiento por proximidad

La consulta sin nombre es cómoda y esconde una dependencia peligrosa: el significado de tu regla depende de la estructura del DOM por encima del componente, que es precisamente lo que las container queries venían a eliminar. Un componente que consulta “mi contenedor más cercano” está afirmando algo sobre su entorno que ni él controla ni puede verificar; el día que alguien envuelva ese componente en un panel que resulta ser contenedor, la consulta cambiará de referente sin que ninguna herramienta lo detecte, sin error de compilación, sin test roto, y con un resultado visual que puede parecer razonable durante meses. Nombrar convierte una dependencia implícita y posicional en una dependencia explícita y nominal, que es la misma migración que hicieron los lenguajes de programación al pasar de argumentos posicionales a argumentos con nombre, y las bases de datos al pasar de SELECT * a columnas enumeradas. La regla que se generaliza: cuando una referencia se resuelve por proximidad, es frágil ante cualquier cambio de estructura que nadie relacionará con ella. Págale el precio de un identificador y duerme tranquilo.