wandres.dev
ELEMENTS III · Overlays de layout

Container queries en el inspector: qué contenedor consulta cada elemento

El distintivo de contenedor, cómo ver el contenedor de consulta de un elemento y su tamaño actual, y las cuatro razones por las que una container query no dispara.

⏱ 15 min

Las container queries cambiaron la unidad de adaptación de la ventana al contenedor, y con ello introdujeron un mecanismo de resolución que no tiene equivalente en el resto de CSS: para saber si una regla aplica hay que averiguar cuál de los ancestros del elemento es su contenedor de consulta, y ese ancestro depende del nombre, del tipo y de la proximidad. Cuando la regla no dispara, la causa está casi siempre en esa resolución y no en la condición.

🎯 Al terminar esta lección sabrás
  • Identificar el contenedor de consulta de un elemento y su tamaño actual desde el panel.
  • Enumerar las cuatro causas por las que una container query no aplica.
  • Explicar por qué un elemento no puede consultarse a sí mismo y qué implica para el marcado.
  • Diagnosticar unidades de contenedor que resuelven a valores inesperados.

El distintivo y lo que muestra

Un elemento con container-type declarado muestra un distintivo en el árbol. Y cuando seleccionas un elemento cuyos estilos dependen de una container query, el panel de estilos muestra la regla @container como encabezado sobre el bloque, con el nombre del contenedor que la está resolviendo y su tamaño actual.

Ese dato es el diagnóstico completo en una línea. Si la regla aparece con su contenedor y su tamaño, la consulta está funcionando y solo hay que comprobar si la condición se cumple con ese tamaño. Si la regla no aparece en absoluto, la consulta no se está resolviendo y el problema es de estructura.

Además, al pasar el cursor sobre el nombre del contenedor en ese encabezado, el panel resalta el elemento contenedor en la página. Eso responde visualmente a la pregunta más importante: cuál de los quince ancestros es el que manda.

Las cuatro causas de que no dispare

Uno: el contenedor no declara container-type. Es la causa más frecuente con diferencia. Poner un nombre con container-name sin declarar el tipo no crea un contenedor de consulta: el nombre es solo una etiqueta. El shorthand container: tarjeta / inline-size declara ambos y es la forma recomendada precisamente por eso.

Los tipos son tres: inline-size permite consultar el tamaño en línea, size permite consultar ambas dimensiones, y normal desactiva las consultas de tamaño pero sigue permitiendo las de estilo. El más usado con diferencia es inline-size, porque size exige que el elemento tenga una altura determinada independientemente de su contenido, lo cual rara vez es cierto.

Dos: el contenedor no es un ancestro. Un elemento consulta a sus ancestros, nunca a sí mismo ni a sus hermanos. Esto tiene una consecuencia estructural que sorprende a todo el mundo la primera vez: un componente no puede adaptarse a su propio tamaño. Si quieres que una tarjeta cambie de disposición según su ancho, la tarjeta tiene que ir envuelta en un contenedor que sea el que declara container-type, y las reglas se aplican a los elementos de dentro. El patrón canónico es un div envolvente que declara el contenedor y el componente real dentro.

La razón de este diseño no es arbitraria: si un elemento pudiera consultar su propio tamaño y cambiar propiedades que afectan a ese tamaño, habría bucles infinitos de resolución. La restricción es lo que hace el sistema computable.

Tres: el nombre no coincide. Una @container tarjeta (...) busca el ancestro más cercano cuyo container-name incluya tarjeta y que tenga un container-type compatible con la consulta. Un error tipográfico en el nombre hace que la regla busque un contenedor que no existe, y en ese caso la regla simplemente no aplica, sin ningún error ni aviso. Una @container sin nombre usa el ancestro con container-type más cercano, sea cual sea, lo cual es cómodo y frágil: basta con que alguien introduzca un contenedor intermedio para que tu consulta cambie de referencia sin que nada lo señale.

Cuatro: el tipo no permite esa consulta. Consultar height contra un contenedor de tipo inline-size no funciona, porque ese tipo solo contiene el eje en línea. La regla no aplica y no hay aviso.

⚠️
Cuidado

El patrón común de las cuatro causas es el silencio. Una container query mal resuelta no produce ningún error, ningún aviso en consola y ninguna regla tachada: la regla sencillamente no aparece en el panel. Esa ausencia es el síntoma, y por eso la comprobación correcta es mirar si la regla @container figura como encabezado, no buscar algo tachado.

Las unidades de contenedor

Junto a las consultas vienen las unidades relativas al contenedor: cqw, cqh, cqi, cqb, cqmin y cqmax. Se resuelven contra el contenedor de consulta más cercano, con la misma lógica de resolución de las consultas.

Y aquí hay una trampa concreta: si no hay ningún contenedor de consulta ancestro, las unidades de contenedor se resuelven contra el viewport pequeño, es decir, se comportan como svw y compañía. No fallan, no avisan: silenciosamente usan otra referencia. El síntoma es un tamaño que parece funcionar pero que no reacciona al redimensionar el contenedor, solo la ventana.

La comprobación desde la consola.

// Encuentra el contenedor de consulta efectivo de un elemento
function contenedorDeConsulta(el, nombre) {
  for (let n = el.parentElement; n; n = n.parentElement) {
    const cs = getComputedStyle(n);
    const tipo = cs.containerType;
    if (tipo && tipo !== 'normal') {
      const nombres = cs.containerName.split(/\s+/).filter(Boolean);
      if (!nombre || nombres.includes(nombre)) {
        return { nodo: n, tipo, nombres, ancho: n.getBoundingClientRect().width };
      }
    }
  }
  return null;
}
console.log(contenedorDeConsulta($0));
console.log(contenedorDeConsulta($0, 'tarjeta'));

Ese fragmento replica la regla de resolución del motor y devuelve el nodo, su tipo y su ancho. Si devuelve null, no hay contenedor y esa es la explicación de todo lo demás.

Declarar un contenedor tiene un coste que no se ve en el inspector

Hay una consecuencia de container-type que ninguna herramienta señala y que produce bugs de layout desconcertantes: declarar un contenedor de tamaño aplica contención, y la contención cambia el comportamiento del elemento de formas que no tienen nada que ver con las consultas. Con container-type: inline-size, el elemento adquiere contención de tamaño en el eje en línea y de estilo, y establece un contexto de formato independiente. En la práctica eso significa dos cosas concretas. Primera: al establecer un contexto de formato independiente se impide el colapso de márgenes con el exterior y los flotantes de dentro no se salen; eso normalmente es bueno y a veces cambia un layout que funcionaba. Segunda: con container-type: size —el que permite consultar altura— el elemento deja de dimensionarse por su contenido en ambos ejes, y si no le das una altura explícita colapsa a cero. Es la causa del clásico “he puesto container-type y el componente ha desaparecido”, y es la razón por la que inline-size es casi siempre la elección correcta.

Y aquí va la parte que más gente sigue teniendo mal, porque durante dos años fue verdad y dejó de serlo sin ruido. Había una tercera consecuencia: el contenedor se convertía en bloque contenedor de sus descendientes fixed y creaba contexto de apilamiento, exactamente igual que un transform. Ya no. El grupo de trabajo concluyó que la contención de layout era más fuerte de lo que las container queries necesitaban —bloqueaba la alineación por línea base, la escapada de los posicionados y la contribución del desbordamiento desplazable— y la quitó del paquete; Chrome 129, Firefox 133 y Safari 18.4 lo aplicaron. La lista canónica de MDN sobre contextos de apilamiento todavía no lo recoge, así que vas a encontrar el dato viejo en fuentes que parecen fiables. La lección que generaliza no es la lista sino el método: en CSS moderno hay un grupo de propiedades —transform, filter, backdrop-filter, will-change, contain, perspective— que capturan a los descendientes posicionados, y ese grupo cambia con el tiempo en las dos direcciones. Cuando algo con position: fixed no se comporta como fijo, no recites la lista de memoria: sube por el árbol en el panel de computados y comprueba cuál de los ancestros lo está capturando de verdad. La comprobación tarda un minuto y no caduca; la lista memorizada caducó hace dos años.