Saltar al contenido
container-queries.css · devschool

Container queries en CSS

Lección 23 de 50 · 13 min de lectura · Actualizado el

En esta lección
  1. El problema que resuelven
  2. Paso 1: declarar un contenedor con container-type
  3. Paso 2: escribir la consulta con @container
  4. Dar nombre a los contenedores
  5. Unidades de contenedor: cqi, cqw y compañía
  6. Style queries: consultar variables CSS
  7. Media queries frente a container queries
  8. Ejemplo completo: una tarjeta que se adapta
  9. Soporte y alternativas
  10. Errores frecuentes
  11. Resumen

Las container queries permiten que un componente cambie su diseño según el espacio que tiene su contenedor, en lugar de según el tamaño de la ventana. En CSS moderno viste un adelanto; aquí vas a verlas a fondo: cómo declarar un contenedor, cómo escribir las consultas con @container, cómo poner nombres, las unidades cqi y compañía, las style queries, en qué se diferencian de las media queries y los errores que más tiempo hacen perder. Al final montarás una tarjeta que se adapta sola a cualquier hueco.

El problema que resuelven

Imagina la tarjeta de una ruta de senderismo: foto, título, una frase y un enlace. La usas en tres sitios de la misma página:

  • En la columna principal, donde mide 700px: quieres la foto a la izquierda y el texto a la derecha.
  • En una barra lateral de 260px: quieres la foto arriba y el texto debajo.
  • En una rejilla de tres columnas: depende de lo ancha que sea la ventana.

Con media queries solo puedes preguntar “¿cuánto mide la ventana?”. Pero en una ventana de 1200px, la tarjeta de la columna principal y la de la barra lateral necesitan diseños distintos a la vez. Acabarías escribiendo clases distintas para cada sitio (.tarjeta-lateral, .tarjeta-principal) y repitiendo reglas.

Con container queries la pregunta cambia a “¿cuánto espacio tengo yo?”. La tarjeta se escribe una sola vez y funciona en cualquier sitio. Es la forma natural de pensar en componentes reutilizables.

Paso 1: declarar un contenedor con container-type

Un elemento no es contenedor por defecto. Tienes que convertirlo con la propiedad container-type:

.columna-principal,
.barra-lateral {
  container-type: inline-size;
}

Valores posibles:

ValorQué vigilaCuándo usarlo
inline-sizeEl ancho (el tamaño en línea)Casi siempre
sizeAncho y altoSolo si el contenedor tiene una altura fija
normalNada de tamaño (valor por defecto)Para style queries (lo verás más abajo)

Inline size significa “tamaño en la dirección en que se escribe el texto”. En español, de izquierda a derecha, es el ancho. Si te suena raro, lo tienes explicado en propiedades lógicas.

¿Por qué hay que declararlo? Porque para que el navegador pueda preguntar “cuánto mide el contenedor” sin entrar en un bucle infinito, el ancho del contenedor no puede depender de lo que tiene dentro. Si dependiera, el hijo cambiaría el ancho del padre, el padre cambiaría el estilo del hijo, y vuelta a empezar. Al poner container-type: inline-size, aceptas esa regla: el ancho del contenedor lo deciden su padre y su propio CSS, nunca sus hijos. Esto se llama contención (containment).

Paso 2: escribir la consulta con @container

Con el contenedor declarado, escribes reglas que solo se aplican cuando el contenedor cumple una condición:

.tarjeta {
  display: grid;
  gap: 12px;
}

@container (width >= 420px) {
  .tarjeta {
    grid-template-columns: 40% 1fr;
  }
}

La tarjeta de la barra lateral (260px) queda en una columna. La de la columna principal (700px) pone la foto a la izquierda. Misma clase, mismo HTML, distinto resultado.

Las condiciones se escriben igual que en las media queries modernas:

@container (width >= 420px) { ... }                        /* desde 420px */
@container (width < 300px) { ... }                         /* menos de 300px */
@container (300px <= width < 600px) { ... }                /* rango */
@container (width >= 400px) and (height >= 300px) { ... }  /* exige size */
@container (orientation: landscape) { ... }                /* más ancho que alto, exige size */
@container not (width >= 400px) { ... }                    /* negación */

También funciona la sintaxis antigua (min-width: 420px), que verás en muchos tutoriales. Las dos son equivalentes.

Las dos reglas de oro

  1. La consulta mira al contenedor más cercano. El navegador sube desde el elemento por sus antepasados y usa el primero que sea contenedor (del tipo adecuado).
  2. Una container query nunca cambia al propio contenedor, solo a sus descendientes. Si quisieras cambiar el contenedor según su propio ancho, volverías al bucle infinito.
.barra-lateral { container-type: inline-size; }

/* MAL: el contenedor no puede consultarse a sí mismo */
@container (width < 300px) {
  .barra-lateral { padding: 8px; }
}

Esa regla no se aplica a .barra-lateral con su propio tamaño: el navegador buscaría un contenedor por encima de ella. Si necesitas cambiar el “marco”, envuelve el contenido en un elemento interior y aplica los cambios a ese.

Dar nombre a los contenedores

Cuando hay contenedores dentro de otros contenedores, a veces no quieres el más cercano sino uno concreto. Para eso está container-name:

.pagina {
  container-type: inline-size;
  container-name: pagina;
}

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

/* Mira a .pagina, aunque haya contenedores más cerca */
@container pagina (width >= 900px) {
  .tarjeta-titulo { font-size: 1.6rem; }
}

El atajo container junta las dos propiedades: primero el nombre, una barra y el tipo.

.barra-lateral {
  container: lateral / inline-size;
}

Un elemento puede tener varios nombres separados por espacios (container-name: lateral estrecho;). Elige nombres que describan la zona, no el tamaño: lateral sigue teniendo sentido si mañana la barra se ensancha; pequeno no.

Consejo: pon nombre a tus contenedores desde el principio. Si más adelante alguien añade un contenedor intermedio, tus consultas sin nombre empezarán a mirar al nuevo y el diseño cambiará sin motivo aparente.

Unidades de contenedor: cqi, cqw y compañía

Igual que vw es el 1 % del ancho de la ventana, existen unidades que son un porcentaje del contenedor:

UnidadEquivale a
cqw1 % del ancho del contenedor
cqh1 % del alto del contenedor (exige size)
cqi1 % del tamaño en línea (en español, el ancho)
cqb1 % del tamaño en bloque (en español, el alto; exige size)
cqminEl menor entre cqi y cqb
cqmaxEl mayor entre cqi y cqb

En la práctica, cqi es la que más usarás. La diferencia con cqw solo se nota en idiomas que se escriben en vertical, así que cqi es la opción más correcta en general.

Su uso estrella es la tipografía fluida dentro de un componente, combinada con clamp():

.tarjeta-titulo {
  /* nunca menos de 1.1rem ni más de 1.8rem; en medio, el 6 % del contenedor */
  font-size: clamp(1.1rem, 6cqi, 1.8rem);
}

.tarjeta {
  padding: clamp(8px, 4cqi, 24px);
}

Si el contenedor mide 300px, 6cqi son 18px; si mide 500px, 30px. El título crece con la tarjeta, no con la ventana.

Dos detalles:

  • Las unidades se calculan respecto al contenedor más cercano del elemento. Si el propio elemento es contenedor, sus unidades cq* miran al contenedor de encima.
  • Si no hay ningún contenedor por encima, las unidades usan la ventana (como svw y svh). No da error, pero el resultado no será el que esperas.

Style queries: consultar variables CSS

Además del tamaño, @container puede preguntar por el valor de una variable CSS del contenedor. Se llaman style queries:

.zona-ofertas {
  --tema: destacado;
}

@container style(--tema: destacado) {
  .tarjeta {
    border: 2px solid #c2410c;
    background: #fff7ed;
  }
}

Todas las tarjetas que estén dentro de .zona-ofertas se pintan como destacadas, sin añadir clases a cada una. Para las style queries no hace falta container-type: todos los elementos son contenedores de estilo por defecto.

¿Para qué sirven si ya existen las clases? Para que un componente reaccione al contexto sin que el contexto tenga que conocer sus clases internas. El que monta la página solo pone --tema: destacado en la zona, y cada componente decide qué significa eso para él.

Nota: de momento solo se pueden consultar variables CSS (las que empiezan por --), no propiedades normales como color. Su soporte llegó más tarde que el de las consultas de tamaño: compruébalo en caniuse.com antes de usarlas en un proyecto real y trátalas como una mejora que puede no aplicarse.

Media queries frente a container queries

No se sustituyen: se complementan.

Media queriesContainer queries
Preguntan porLa ventana o el dispositivoUn elemento antepasado
Se escriben con@media@container
Necesitan preparar algoNocontainer-type en el contenedor
Pueden consultarAncho, alto, orientación, preferencias del usuario (prefers-color-scheme…), tipo de punteroAncho, alto, orientación del contenedor; variables CSS
Unidades propiasvw, vh, dvh…cqi, cqb, cqw…
Ideales paraEl esqueleto de la página: columnas, menú, cabeceraComponentes reutilizables: tarjetas, widgets, formularios

Una forma sencilla de decidir: si la pregunta es sobre la página (“¿el menú cabe en horizontal?”), usa @media. Si es sobre una pieza (“¿esta tarjeta tiene sitio para la foto al lado?”), usa @container. Y las preferencias del usuario (modo oscuro, movimiento reducido) siempre van con @media.

Ejemplo completo: una tarjeta que se adapta

Vamos a montar la tarjeta de ruta en tres diseños según su espacio: estrecha (foto arriba), media (foto a la izquierda) y ancha (foto a la izquierda y datos en columnas).

<div class="pagina">
  <main class="principal">
    <article class="ruta">
      <img src="cares.jpg" alt="Desfiladero del Cares con el río al fondo">
      <div class="ruta-texto">
        <h2 class="ruta-titulo">Ruta del Cares</h2>
        <p>Un sendero excavado en la roca entre Poncebos y Caín.</p>
        <ul class="ruta-datos">
          <li>12 km</li>
          <li>Dificultad media</li>
          <li>4 h</li>
        </ul>
      </div>
    </article>
  </main>
  <aside class="lateral">
    <article class="ruta">
      <img src="lagos.jpg" alt="Lago Enol rodeado de prados">
      <div class="ruta-texto">
        <h2 class="ruta-titulo">Lagos de Covadonga</h2>
        <p>Circular sencilla entre el Enol y el Ercina.</p>
        <ul class="ruta-datos">
          <li>6 km</li>
          <li>Fácil</li>
          <li>2 h</li>
        </ul>
      </div>
    </article>
  </aside>
</div>
/* Esqueleto de la página: media query */
.pagina {
  display: grid;
  gap: 24px;
}

@media (width >= 900px) {
  .pagina { grid-template-columns: 1fr 280px; }
}

/* Contenedores */
.principal,
.lateral {
  container: zona / inline-size;
}

/* Componente: diseño base (estrecho) */
.ruta {
  display: grid;
  gap: 12px;
  background: white;
  border-radius: 8px;
  overflow: hidden;
}

.ruta img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  display: block;
}

.ruta-texto { padding: 0 16px 16px; }

.ruta-titulo {
  margin: 0;
  font-size: clamp(1.1rem, 5cqi, 1.8rem);
}

.ruta-datos {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Diseño medio: foto a la izquierda */
@container zona (width >= 420px) {
  .ruta { grid-template-columns: 40% 1fr; }
  .ruta img { height: 100%; aspect-ratio: auto; }
  .ruta-texto { padding: 16px 16px 16px 0; }
}

/* Diseño ancho: datos en tres columnas iguales */
@container zona (width >= 650px) {
  .ruta-datos {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    text-align: center;
  }
}

Fíjate en el reparto de trabajo: la media query decide si la barra lateral va al lado o debajo; las container queries deciden cómo se dibuja cada tarjeta en el hueco que le ha tocado. En un móvil, la tarjeta de la barra lateral ocupa todo el ancho y se ve igual de bien que la principal. En un ordenador, cada una toma un diseño distinto sin una sola clase extra.

En el ejemplo de esta lección (botón “Pruébalo”) tienes una versión con un contenedor que puedes estirar con el ratón gracias a resize: horizontal. Es la mejor forma de ver la consulta en acción.

Soporte y alternativas

Las container queries de tamaño y las unidades cq* funcionan en todos los navegadores actuales desde 2023. Si tienes que dar servicio a navegadores muy antiguos, escribe primero un diseño base que funcione sin ellas (la versión estrecha) y deja las mejoras dentro de @container: el navegador que no lo entienda simplemente ignorará esas reglas. Si alguna vez necesitas comprobarlo de forma explícita, existe @supports (container-type: inline-size) { ... }.

Para depurar, abre DevTools: en el panel Elements, los contenedores llevan una etiqueta container, y al seleccionar un elemento afectado por una @container, el panel Styles muestra la consulta y a qué contenedor apunta.

Errores frecuentes

  • Olvidar container-type. Sin él, @container no encuentra contenedor y la regla nunca se aplica.
  • Querer cambiar el propio contenedor. Las consultas solo afectan a los descendientes; envuelve el contenido en otro elemento.
  • El contenedor que se encoge a cero. Con container-type: inline-size, el ancho ya no depende del contenido. Si el contenedor es un elemento flotante, inline-block o un hijo flex sin ancho definido, puede quedarse en 0px y el contenido se desborda. Dale un ancho (width, flex: 1, una columna de Grid…).
  • Usar size sin altura. container-type: size también ignora el contenido para el alto; si no le das una altura, el contenedor mide 0px de alto.
  • Consultas sin nombre con contenedores anidados. La consulta mira al más cercano, que quizá no es el que pensabas. Usa container-name.
  • Usar @container para todo. El esqueleto de la página y las preferencias del usuario siguen siendo cosa de @media.
  • Unidades cqi sin contenedor. Funcionan, pero miden la ventana, y el texto no se adapta al componente.

Resumen

HerramientaPara quéEjemplo
container-type: inline-sizeConvertir un elemento en contenedor de ancho.lateral { container-type: inline-size; }
container-nameElegir un contenedor concretocontainer-name: lateral;
containerAtajo de nombre y tipocontainer: lateral / inline-size;
@containerEstilos según el tamaño del contenedor@container (width >= 420px) { ... }
@container nombreConsultar un contenedor con nombre@container lateral (width < 300px)
cqi, cqw, cqb, cqhMedidas relativas al contenedorfont-size: clamp(1rem, 5cqi, 1.6rem);
style()Consultar una variable CSS del contenedor@container style(--tema: destacado)
@mediaEsqueleto de la página y preferencias@media (width >= 900px)

Las container queries hablan de “tamaño en línea” y “tamaño en bloque”. En la siguiente lección verás de dónde vienen esos nombres: propiedades lógicas.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

container-queries.html
Resultado

Pon a prueba lo que has aprendido

[CSS] ¿Qué ventaja tiene @container frente a @media para una tarjeta que se usa en la columna principal y en la barra lateral de la misma página?

[CSS] ¿Por qué el padding de .lateral no cambia cuando mide menos de 300px?
.lateral {
  container-type: inline-size;
}

@container (width < 300px) {
  .lateral { padding: 8px; }
}

[CSS] Si .tarjeta está dentro de un contenedor de 400px de ancho, ¿qué tamaño de letra tiene el título?
.zona { container-type: inline-size; }

.tarjeta h2 {
  font-size: clamp(16px, 5cqi, 32px);
}

[CSS] Un hijo de un contenedor flex tiene container-type: inline-size y ningún ancho definido. De repente se queda a 0px de ancho. ¿Por qué?

[CSS] ¿Qué consulta esta regla?
@container style(--tema: destacado) {
  .tarjeta { border: 2px solid #c2410c; }
}

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.