wandres.dev
CONTAINER STYLE QUERIES · Consultar custom properties

Combinar tamaño y estilo en una arquitectura

Un contenedor que responde a las dos preguntas, por qué @container no aporta especificidad y qué gobierna entonces los conflictos, y el reparto de responsabilidades que funciona en producción.

⏱ 18 min

Un componente maduro reacciona a dos cosas independientes: cuánto espacio le han dado y en qué modo le han pedido que se pinte. Las dos preguntas se hacen con el mismo at-rule y se pueden combinar en una sola condición, pero al hacerlo aparece un detalle que sorprende a todo el mundo: @container no aporta ninguna especificidad, así que dos bloques que se solapan se resuelven por orden de aparición. Entender eso es lo que evita las cascadas accidentales.

🎯 Al terminar esta lección sabrás
  • Combinar consultas de tamaño y de estilo en una sola condición o en bloques separados.
  • Declarar un contenedor que sirva a los dos tipos de consulta con un solo nombre.
  • Predecir qué regla gana cuando dos bloques @container afectan al mismo selector.
  • Repartir las responsabilidades entre tamaño, estilo, atributo y media query.

Un contenedor, dos preguntas

Recuerda que las consultas de estilo no necesitan container-type, pero sí se benefician de container-name para no depender del padre inmediato. Un contenedor completo se declara así:

.ficha-wrap {
  container-name: ficha;
  container-type: inline-size;
}

Con eso, @container ficha (...) funciona para tamaño y @container ficha style(...) para estilo, contra el mismo elemento y sin ambigüedad.

Las condiciones se combinan con los operadores habituales:

/* las dos cosas a la vez */
@container ficha (inline-size >= 30rem) and style(--superficie: oscura) {
  .ficha__img { box-shadow: 0 0 0 1px color-mix(in oklch, white 20%, transparent); }
}

/* una u otra */
@container ficha (inline-size < 16rem) or style(--densidad: compacta) {
  .ficha__entrada { display: none; }
}

Esa última condición es un ejemplo bonito de por qué esto compone bien: “oculta el resumen si hay poco sitio o si me han pedido modo compacto” son dos motivos distintos para la misma decisión, y expresarlos juntos deja claro que la decisión es una sola.

La especificidad que no existe

@container, igual que @media y @supports, no añade nada a la especificidad de los selectores que contiene. Un .ficha__titulo dentro de una container query tiene exactamente la misma especificidad que uno fuera. Cuando dos declaraciones compiten con la misma especificidad, gana la que aparezca después en la hoja.

@container ficha (inline-size >= 30rem) {
  .ficha__titulo { font-size: 2rem; }
}

.ficha__titulo { font-size: 1.2rem; }   /* GANA: viene despues */

Ese ejemplo está mal escrito y es un error real que se comete a diario. La disciplina que lo evita es la de siempre: el estado base primero, las condiciones después. Y si el orden no lo puedes controlar porque los estilos vienen de sitios distintos, ordénalo con capas en cascada, que sí tienen un orden explícito y no dependen de quién importó qué antes:

@layer base, adaptativo;

@layer base {
  .ficha__titulo { font-size: 1.2rem; }
}

@layer adaptativo {
  @container ficha (inline-size >= 30rem) { .ficha__titulo { font-size: 2rem; } }
  @container ficha style(--densidad: compacta) { .ficha__titulo { font-size: 1.05rem; } }
}

Dentro de la capa adaptativa siguen compitiendo por orden, así que coloca lo más específico conceptualmente al final. Con dos bloques que pueden cumplirse a la vez, el segundo gana; si eso no es lo que quieres, hazlos mutuamente excluyentes añadiendo la negación del otro.

⚠️
Dos condiciones que se solapan no se combinan: compiten

Si tienes un bloque para inline-size >= 30rem y otro para style(--densidad: compacta), y se cumplen los dos, no obtienes una fusión inteligente: obtienes las declaraciones de los dos bloques con las coincidentes resueltas por orden. La forma de tener control es escribir explícitamente el caso combinado con and, o excluir con not.

El reparto de responsabilidades

Este es el resumen que merece la pena tener a mano al diseñar un componente. Cada tipo de decisión tiene un mecanismo natural, y mezclarlos es la fuente de la mayoría del CSS enredado.

Lo que decide Quién lo sabe Mecanismo
Cuánto espacio hay el layout que lo coloca consulta de tamaño
En qué modo se pinta un ancestro cualquiera consulta de estilo
Qué variante es este elemento quien lo escribe en el marcado atributo o clase
Qué contiene el elemento el propio elemento :has()
Cómo es el dispositivo o qué prefiere el usuario el navegador media query

Aplicado a la ficha del nivel anterior, el resultado es un componente con cuatro entradas independientes y ninguna dependencia global:

.ficha-wrap { container: ficha / inline-size; }

.ficha {
  --paso: clamp(0.7rem, 0.45rem + 1.1cqi, 1.6rem);
  display: grid;
  gap: var(--paso);
  padding: var(--paso);
}

/* espacio */
@container ficha (inline-size >= 26rem) {
  .ficha { grid-template-columns: minmax(9rem, 32%) minmax(0, 1fr); }
}

/* modo, decidido por un ancestro */
@container ficha style(--superficie: oscura) {
  .ficha { border-color: color-mix(in oklch, currentColor 30%, transparent); }
}

/* variante propia, decidida en el marcado */
.ficha[data-destacada] { border-inline-start: 3px solid currentColor; }

/* contenido */
.ficha:not(:has(.ficha__img)) { grid-template-columns: 1fr; }

/* preferencia del usuario */
@media (prefers-reduced-motion: reduce) {
  .ficha { transition: none; }
}

Cinco mecanismos, cinco tipos de información, cero solapamiento. Ese es el objetivo: que cada regla se pueda leer sabiendo exactamente qué la dispara.

Qué hacer hoy, en la práctica

Con el soporte de mayo de 2026 recién estrenado, la recomendación honesta para un proyecto en producción es esta.

Usa consultas de tamaño sin reservas: llevan desde 2023 en todos los motores y son la base de cualquier componente portable.

Usa consultas de estilo para refinamiento visual —bordes, sombras, mezclas de color, ajustes de contraste sobre superficies— donde su ausencia produzca un componente algo menos pulido y nada más.

No las uses para decisiones estructurales, para ocultar o mostrar información, ni para nada que un usuario necesite percibir. Para eso, un atributo en el marcado sigue siendo lo correcto, funciona en todas partes y además puede llevar información a la capa de accesibilidad.

Y revisa el calendario dentro de un año: cuando esta función pase a estar ampliamente disponible, buena parte de este párrafo dejará de aplicar y podrás mover cosas de la columna del atributo a la de la consulta de estilo.

Una fuente de verdad por cada tipo de decisión

El resumen de todo este tramo, del 21 al 26, se puede comprimir en un solo principio, y no es sobre CSS: cada decisión debe tener exactamente un mecanismo que la exprese, y ese mecanismo debe ser el que tiene acceso a la información que la decisión necesita. Cuando ese principio se rompe —cuando el espacio disponible se infiere de una media query, cuando el modo se acopla a una clase de ancestro, cuando el estado del contenido se replica en un atributo que hay que sincronizar a mano— no obtienes un sistema roto, obtienes uno que funciona hoy y que acumula puntos donde dos fuentes de verdad pueden discrepar. Y discreparán, porque nadie las mantiene sincronizadas: no hay ningún test que compruebe que la clase .compacta sigue puesta donde el ancho es pequeño. El valor real de las container queries no está en ninguna cosa nueva que permitan dibujar; está en que por fin existe un mecanismo para cada tipo de pregunta, y por tanto por primera vez es posible escribir CSS donde ninguna regla dependa de una coincidencia que alguien tiene que recordar mantener. Esa es la diferencia entre un sistema que envejece y uno que simplemente sigue funcionando.