Container Queries: componentes que se adaptan a su contenedor
Crea componentes responsivos con container queries que se adaptan a su contenedor y no al viewport: el mismo layout sirve en barras laterales y modales.

Las media queries responden a la pregunta equivocada para el diseño basado en componentes. Cuando escribes @media (min-width: 768px), estás preguntando "¿qué tan ancho es el viewport?". Pero lo que tu componente realmente necesita saber es "¿cuánto espacio tengo?". Un componente de tarjeta en una barra lateral estrecha y la misma tarjeta en un área de contenido amplia comparten el mismo ancho de viewport pero necesitan layouts completamente diferentes.
Las container queries resuelven esto permitiendo que los componentes respondan a las dimensiones de su contenedor en lugar del viewport. Esto hace que los componentes sean verdaderamente portátiles: colócalos en cualquier contexto de layout y se adaptan automáticamente. Es la primitiva de diseño responsivo que hemos querido desde que empezamos a construir bibliotecas de componentes.
Fundamentos de las Container Queries
Para usar container queries, defines un contexto de contención en el elemento padre y luego escribes queries contra ese contenedor desde el hijo.
/* ❌ Media queries: component responds to viewport */
.card {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 600px) {
.card {
grid-template-columns: 200px 1fr;
}
}
/* This card always switches at 600px viewport width
even if it's in a 300px sidebar */
/* ✅ Container queries: component responds to its container */
.card-container {
container-type: inline-size;
container-name: card;
}
.card {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
padding: 1rem;
}
@container card (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
}
}
@container card (min-width: 700px) {
.card {
grid-template-columns: 250px 1fr;
gap: 2rem;
padding: 2rem;
}
}<!-- Same component, different contexts, different layouts -->
<!-- In sidebar: stays vertical (container < 400px) -->
<aside class="sidebar">
<div class="card-container">
<article class="card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="card-content">
<h3>Article Title</h3>
<p>Description text here...</p>
</div>
</article>
</div>
</aside>
<!-- In main content: goes horizontal (container > 400px) -->
<main class="content">
<div class="card-container">
<article class="card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="card-content">
<h3>Article Title</h3>
<p>Description text here...</p>
</div>
</article>
</div>
</main>Unidades de Container Queries
Las container queries introducen nuevas unidades relativas a las dimensiones del contenedor, reemplazando las unidades de viewport para el dimensionamiento interno de los componentes.
.card-container {
container-type: inline-size;
container-name: card;
}
/* Container query units */
.card-title {
/* cqi = 1% of container's inline size */
font-size: clamp(1rem, 3cqi, 2rem);
}
.card-image {
/* cqw = 1% of container width */
/* cqh = 1% of container height */
/* cqi = 1% of container inline size */
/* cqb = 1% of container block size */
/* cqmin = smaller of cqi and cqb */
/* cqmax = larger of cqi and cqb */
height: 30cqi;
object-fit: cover;
}
/* Fluid spacing based on container */
.card {
padding: clamp(0.75rem, 3cqi, 2rem);
gap: clamp(0.5rem, 2cqi, 1.5rem);
}Patrones de Componentes del Mundo Real
Aquí hay patrones prácticos que demuestran dónde las container queries resuelven problemas que las media queries no pueden.
/* Navigation that adapts to its container */
.nav-container {
container-type: inline-size;
container-name: nav;
}
.nav-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
list-style: none;
padding: 0;
}
.nav-label {
display: none;
}
.nav-icon {
width: 24px;
height: 24px;
}
/* When nav has enough space, show labels */
@container nav (min-width: 200px) {
.nav-list {
gap: 0.5rem;
}
.nav-label {
display: inline;
}
}
/* When nav has lots of space, go horizontal */
@container nav (min-width: 600px) {
.nav-list {
flex-direction: row;
gap: 1rem;
}
}/* Data table that adapts its columns to available space */
.table-container {
container-type: inline-size;
container-name: data-table;
}
.data-table {
width: 100%;
}
/* Hide lower-priority columns when space is tight */
.col-priority-low {
display: none;
}
.col-priority-medium {
display: none;
}
@container data-table (min-width: 500px) {
.col-priority-medium {
display: table-cell;
}
}
@container data-table (min-width: 800px) {
.col-priority-low {
display: table-cell;
}
}
/* Switch to card layout on very narrow containers */
@container data-table (max-width: 350px) {
.data-table,
.data-table thead,
.data-table tbody,
.data-table tr,
.data-table td {
display: block;
}
.data-table thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.data-table td::before {
content: attr(data-label);
font-weight: 600;
display: block;
margin-bottom: 0.25rem;
}
.data-table tr {
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
}Combinando Container Queries con Style Queries
Las style queries (CSS @container style()) te permiten consultar el estilo computado de un contenedor, permitiendo que los componentes respondan al estado sin JavaScript.
/* Style queries: respond to custom property values */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Dark variant via custom property */
.card-container[data-theme="dark"] {
--card-theme: dark;
}
@container card style(--card-theme: dark) {
.card {
background: #1e293b;
color: #f1f5f9;
}
.card-title {
color: #e2e8f0;
}
}
/* Featured variant */
.card-container[data-featured] {
--card-featured: true;
}
@container card style(--card-featured: true) {
.card {
border-left: 4px solid var(--accent-color);
background: var(--featured-bg);
}
.card-image {
aspect-ratio: 16 / 9;
}
}Contenedores Anidados
Los contenedores pueden anidarse, y cada container query hace referencia al ancestro más cercano con el nombre de contenedor coincidente.
/* Page layout: outer container */
.page-container {
container-type: inline-size;
container-name: page;
}
/* Card grid: inner container */
.card-grid-container {
container-type: inline-size;
container-name: grid;
}
/* Individual card: innermost container */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Grid responds to its container */
.card-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@container grid (min-width: 500px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@container grid (min-width: 900px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Card responds to its own container */
/* (which gets narrower as more columns appear) */
@container card (min-width: 300px) {
.card {
grid-template-columns: 120px 1fr;
}
}Estrategia de Migración: de Media Queries a Container Queries
No tienes que reescribir todo de una vez. Migra componente por componente, empezando por los que aparecen en múltiples contextos de layout.
/* Step 1: Identify components that need container awareness */
/* Good candidates: cards, navigation, data tables, form layouts */
/* Bad candidates: full-page layouts (viewport is the container) */
/* Step 2: Add containment without changing behavior */
.card-wrapper {
container-type: inline-size;
/* containment has no visual effect — safe addition */
}
/* Step 3: Convert media queries to container queries one at a time */
/* Before */
@media (min-width: 768px) {
.card { grid-template-columns: 200px 1fr; }
}
/* After — test in multiple contexts */
@container (min-width: 400px) {
.card { grid-template-columns: 200px 1fr; }
}
/* Step 4: Keep media queries for truly viewport-dependent styles */
/* Page-level layouts, sticky headers, print styles */
@media (min-width: 1024px) {
.page-layout {
grid-template-columns: 280px 1fr;
}
}Conclusiones Clave
Las container queries permiten que los componentes respondan a las dimensiones de su contenedor en lugar del viewport: una tarjeta que se vuelve horizontal con 400px de espacio disponible funciona correctamente ya sea que esté en una barra lateral de 300px (permanece vertical) o en un área de contenido de 800px (se vuelve horizontal), sin ningún cambio de código. Usa container-type: inline-size en los elementos padre y escribe reglas @container (min-width: ...) en los hijos, combinadas con unidades de container query como cqi para tipografía y espaciado fluidos que escalan en relación al espacio real del componente en lugar de la pantalla. Empieza migrando los componentes que aparecen en los contextos de layout más variados —tarjetas, navegación, tablas de datos— porque estos son los que más se benefician de la conciencia del contenedor, mientras que los layouts de nivel de página que genuinamente dependen de las dimensiones del viewport deben permanecer con media queries. Anida contenedores al construir layouts complejos para que cada nivel responda de forma independiente: un grid que ajusta su número de columnas según el espacio disponible contiene tarjetas que ajustan su layout interno según el espacio de cada tarjeta, creando composiciones que se adaptan en cada nivel sin coordinar breakpoints.


