CSS Container Queries representan el avance más significativo en diseño responsive desde los media queries. Los componentes responden al tamaño de su contenedor, no del viewport.
¿Qué son los Container Queries?
Los container queries aplican estilos basados en el tamaño del contenedor padre.
/* The Problem: A card component in different contexts */
/* In main content (800px wide) → horizontal layout */
/* In sidebar (300px wide) → vertical layout */
/* In footer (200px wide) → minimal layout */
/* With media queries, you cannot distinguish these contexts
because they all exist at the same viewport width.
With container queries, each container context triggers
different styles automatically. */Container Queries vs Media Queries
Los media queries solo responden al tamaño del viewport.
| Feature | Media Queries | Container Queries |
|---|---|---|
| Responds to | Viewport size | Container size |
| Scope | Global (page-level) | Local (component-level) |
| Reusability | Breaks in different contexts | Works in any context |
| Use case | Page layout | Component layout |
| Units | vw, vh, vmin, vmax | cqw, cqh, cqi, cqb |
| Browser support | Universal | All modern browsers (2023+) |
Entendiendo el Containment
Los container queries requieren containment explícito.
Tipos de containment
/* container-type values */
/* inline-size: Containment on the inline axis only (width) */
/* Most common — use this in most cases */
.card-wrapper {
container-type: inline-size;
}
/* size: Containment on both inline and block axes (width + height) */
/* Use when you need to query both dimensions */
.widget-wrapper {
container-type: size;
}
/* normal (default): No containment — cannot be queried */
.regular-div {
container-type: normal; /* This is the default */
}
/* IMPORTANT: container-type: size prevents auto-height behavior.
The element will not grow to fit its content on the block axis.
Use inline-size unless you specifically need height queries. */Sintaxis de Container Queries
Dos partes: definir un contenedor y escribir queries.
/* Step 1: Define a container */
.card-container {
container-type: inline-size;
}
/* Step 2: Write container queries */
.card {
/* Default styles (smallest) */
display: grid;
gap: 12px;
padding: 16px;
}
.card__image {
width: 100%;
aspect-ratio: 16/9;
border-radius: 8px;
object-fit: cover;
}
.card__title {
font-size: 1rem;
font-weight: 700;
}
/* When container is at least 400px wide */
@container (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
align-items: center;
}
.card__title {
font-size: 1.25rem;
}
}
/* When container is at least 600px wide */
@container (min-width: 600px) {
.card {
grid-template-columns: 280px 1fr;
gap: 24px;
padding: 24px;
}
.card__title {
font-size: 1.5rem;
}
.card__description {
display: block; /* Show description only in wide containers */
}
}Contenedores nombrados
Nombrar contenedores para apuntar a ancestros específicos.
/* Name containers to target specific ancestors */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: main;
}
/* Query a specific named container */
@container sidebar (max-width: 300px) {
.widget {
font-size: 0.875rem;
}
}
@container main (min-width: 800px) {
.article-card {
grid-template-columns: 1fr 1fr;
}
}
/* Without a name, @container queries the NEAREST ancestor
with containment. Named queries skip to the named container. */Atajo container
Combina container-type y container-name.
/* Shorthand: container: <name> / <type> */
.sidebar {
container: sidebar / inline-size;
}
/* Equivalent to: */
.sidebar {
container-name: sidebar;
container-type: inline-size;
}
/* Multiple names (for querying by either name) */
.widget-area {
container: widget-area panel / inline-size;
}Unidades Container Query
Nuevas unidades CSS relativas al contenedor.
| Unit | Description | Equivalent |
|---|---|---|
| cqw | 1% of container width | Similar to vw for viewport |
| cqh | 1% of container height | Similar to vh for viewport |
| cqi | 1% of container inline size | Width in horizontal writing |
| cqb | 1% of container block size | Height in horizontal writing |
| cqmin | Smaller of cqi and cqb | Similar to vmin |
| cqmax | Larger of cqi and cqb | Similar to vmax |
/* Fluid typography with container query units */
.card-title {
/* Font size scales with container width */
/* Minimum 1rem, maximum 2rem, scales at 5cqi */
font-size: clamp(1rem, 5cqi, 2rem);
}
.card-padding {
/* Responsive padding based on container */
padding: clamp(12px, 4cqi, 32px);
}
.hero-text {
/* Large text that scales with its container */
font-size: clamp(2rem, 8cqi, 5rem);
line-height: 1.1;
}Ejemplos prácticos
Tarjeta responsive
Una tarjeta que se adapta al espacio disponible.
/* Responsive card component */
.card-container {
container: card / inline-size;
}
.card {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
border-radius: 12px;
background: var(--card-bg);
border: 1px solid var(--border-color);
}
.card__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 8px;
}
.card__meta {
display: none; /* Hidden in small containers */
}
.card__actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
/* Medium card (400px+) — side-by-side layout */
@container card (min-width: 400px) {
.card {
flex-direction: row;
align-items: flex-start;
}
.card__image {
width: 180px;
flex-shrink: 0;
aspect-ratio: 1;
}
.card__meta {
display: flex;
gap: 12px;
font-size: 0.875rem;
color: var(--text-muted);
}
}
/* Large card (700px+) — featured layout */
@container card (min-width: 700px) {
.card {
flex-direction: column;
padding: 0;
overflow: hidden;
}
.card__image {
width: 100%;
height: 300px;
border-radius: 0;
}
.card__content {
padding: 24px;
}
.card__title {
font-size: 1.75rem;
}
}Navegación responsive
Navegación que cambia su layout según el ancho del contenedor.
/* Navigation that adapts to its container */
.nav-container {
container: nav / inline-size;
}
.nav {
display: flex;
flex-direction: column;
gap: 4px;
}
.nav__item {
padding: 8px 12px;
border-radius: 6px;
font-size: 0.875rem;
}
.nav__item-icon {
display: none;
}
/* Horizontal layout when container is wide enough */
@container nav (min-width: 500px) {
.nav {
flex-direction: row;
gap: 8px;
}
.nav__item {
white-space: nowrap;
}
}
/* Show icons when there is more space */
@container nav (min-width: 700px) {
.nav__item-icon {
display: inline-flex;
margin-right: 6px;
}
.nav__item {
padding: 10px 16px;
}
}Elementos de grid adaptativos
Elementos que cambian según el ancho de columna.
/* Grid items that adapt to their column width */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
}
/* Each grid item is a container */
.grid__item {
container: grid-item / inline-size;
}
.product {
text-align: center;
padding: 16px;
}
.product__price {
font-size: 1.5rem;
font-weight: 700;
}
.product__details {
display: none;
}
@container grid-item (min-width: 350px) {
.product {
display: grid;
grid-template-columns: 120px 1fr;
text-align: left;
gap: 16px;
}
.product__details {
display: block;
}
}Style Container Queries
Consultar valores de estilo calculados.
/* Style container queries — query computed style values */
/* Define a container with a custom property */
.theme-container {
container-type: normal; /* No size containment needed for style queries */
--theme: light;
}
.theme-container.dark {
--theme: dark;
}
/* Query the custom property value */
@container style(--theme: dark) {
.card {
background: #1a1a2e;
color: #e0e0e0;
border-color: #333;
}
.button {
background: #4a90d9;
color: white;
}
}
@container style(--theme: light) {
.card {
background: #ffffff;
color: #333;
border-color: #e0e0e0;
}
}
/* Practical use: component variants via CSS properties */
.alert-container {
--variant: info;
}
@container style(--variant: error) {
.alert {
background: #fef2f2;
border-left: 4px solid #ef4444;
color: #991b1b;
}
}
@container style(--variant: success) {
.alert {
background: #f0fdf4;
border-left: 4px solid #22c55e;
color: #166534;
}
}Container Queries anidados
Comportamiento responsive complejo con múltiples contenedores.
/* Nested container queries */
.page {
container: page / inline-size;
}
.sidebar {
container: sidebar / inline-size;
}
/* Different behavior based on multiple container sizes */
@container page (min-width: 1200px) {
.sidebar {
width: 350px;
}
}
@container sidebar (max-width: 250px) {
.sidebar-widget {
font-size: 0.875rem;
padding: 8px;
}
.sidebar-widget__title {
font-size: 1rem;
}
}
@container sidebar (min-width: 300px) {
.sidebar-widget {
padding: 16px;
}
.sidebar-widget__chart {
display: block;
}
}Patrones de diseño
Diseño intrínseco
Combinar con CSS Grid auto-fit.
/* Intrinsic design: Grid + Container Queries */
.auto-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}
.auto-grid > * {
container-type: inline-size;
}
/* Items automatically adapt as the grid reflows */
/* No breakpoints needed — the grid column width drives the layout */
@container (min-width: 400px) {
.feature-card {
grid-template-columns: auto 1fr;
}
}
@container (min-width: 500px) {
.feature-card__description {
font-size: 1.125rem;
}
}Widgets de dashboard
Widgets que se adaptan a su celda.
/* Dashboard widget pattern */
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.dashboard__cell {
container: widget / inline-size;
}
.widget {
padding: 16px;
border-radius: 12px;
background: var(--widget-bg);
}
.widget__chart {
height: 150px;
}
.widget__table {
display: none; /* Hidden in small widgets */
}
@container widget (min-width: 400px) {
.widget__chart {
height: 200px;
}
.widget__stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
}
@container widget (min-width: 600px) {
.widget {
padding: 24px;
}
.widget__table {
display: table;
}
.widget__chart {
height: 280px;
}
}Soporte del navegador
Excelente soporte en 2026.
Estrategias de fallback
Usar @supports para fallbacks.
/* Graceful fallback with @supports */
.card-container {
/* Fallback: use media queries */
}
@supports (container-type: inline-size) {
.card-container {
container-type: inline-size;
}
}
/* Fallback styles using media queries */
@media (min-width: 768px) {
.card {
grid-template-columns: 200px 1fr;
}
}
/* Container query override (when supported) */
@container (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
}
}Rendimiento
Entender el containment es importante.
- Crea nuevo contexto de apilamiento.
- Evitar anidamiento innecesario.
- Usar inline-size cuando sea posible.
- Sin pases de layout adicionales.
- Usar contenedores nombrados.
Mejores prácticas
- Container queries para componentes, media queries para páginas.
- Empezar con inline-size.
- Nombrar contenedores.
- Usar unidades container query.
- Combinar con Grid y Flexbox.
- Diseñar de menor a mayor.
- Fallbacks con @supports.
Migración desde media queries
No es necesario reemplazar todos los media queries.
/* Decision Guide: Container Query vs Media Query */
/* USE CONTAINER QUERIES when:
✅ Component is reused in different layout contexts
✅ Component goes in sidebars, modals, grids, etc.
✅ You need component-level responsive behavior
✅ The component's layout depends on available space
USE MEDIA QUERIES when:
✅ Changing the overall page layout
✅ Showing/hiding entire sections
✅ Adjusting global typography or spacing
✅ Changing navigation from desktop to mobile
✅ Print stylesheets
USE BOTH when:
✅ Page layout switches at viewport breakpoints (media query)
✅ Components within the layout adapt to their containers (container query)
*/Conclusión
CSS Container Queries cambian fundamentalmente el diseño responsive. Construya sistemas de diseño modulares y mantenibles.
FAQ
¿Diferencia entre container y media queries?
Media queries: viewport. Container queries: contenedor padre.
¿container-type en cada padre?
No, solo en los elementos contenedor.
¿Compatible con Grid/Flexbox?
Sí, perfectamente.
¿inline-size vs size?
inline-size: solo eje inline. size: ambos ejes.