Media queries respond to the viewport, so the same component can look wrong when placed in a narrow sidebar versus a wide main column. Container queries solve this by letting you style an element based on the size of its container.
The problem with media queries
Consider a card component with media queries:
.card {
display: block;
}
@media (min-width: 600px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
This card switches to a two-column layout at 600px viewport width. But what if the card is in a 300px sidebar? It still uses the two-column layout because the viewport is wide, not because the card has space.
How container queries work
Container queries check the size of a container element, not the viewport:
1. Define a container
.card-wrapper {
container-type: inline-size;
}
2. Query the container
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Now the card switches layout when its wrapper is at least 400px wide, regardless of viewport size.
Container types
| Value | Description |
|---|---|
inline-size |
Query inline dimension (width in horizontal mode) |
size |
Query both inline and block dimensions |
normal |
No containment (default) |
Use inline-size for most cases. size is needed only when querying height, which can cause layout issues.
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: main;
}
Named containers
Name containers to target them explicitly:
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
@container sidebar (min-width: 200px) {
.sidebar-card {
padding: 1.5rem;
}
}
@container sidebar (max-width: 199px) {
.sidebar-card {
padding: 0.75rem;
}
}
Without names, @container queries the nearest ancestor container.
Container query syntax
/* Min-width */
@container (min-width: 400px) { }
/* Max-width */
@container (max-width: 399px) { }
/* Range syntax */
@container (400px <= width <= 800px) { }
/* Named container */
@container sidebar (min-width: 200px) { }
/* Combining conditions */
@container (min-width: 400px) and (max-width: 800px) { }
Container query units
Use container-relative units for sizing:
| Unit | Description |
|---|---|
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 or cqb |
cqmax |
Larger of cqi or cqb |
.card-title {
font-size: clamp(1rem, 5cqi, 2rem);
}
.card-image {
width: 50cqi;
}
These units scale with the container, not the viewport.
Practical examples
Responsive card
.card-wrapper {
container-type: inline-size;
}
.card {
display: flex;
flex-direction: column;
padding: 1rem;
}
.card-image {
width: 100%;
aspect-ratio: 16/9;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
gap: 1.5rem;
}
.card-image {
width: 40%;
}
}
@container (min-width: 600px) {
.card {
padding: 2rem;
}
.card-title {
font-size: 1.5rem;
}
}
Adaptive navigation
.nav-wrapper {
container-type: inline-size;
}
.nav-items {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.nav-label {
display: none;
}
@container (min-width: 200px) {
.nav-items {
flex-direction: row;
flex-wrap: wrap;
}
.nav-label {
display: inline;
}
}
Grid that adapts to container
.grid-wrapper {
container-type: inline-size;
}
.grid {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@container (min-width: 400px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@container (min-width: 700px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
Container queries vs media queries
| Aspect | Media queries | Container queries |
|---|---|---|
| Based on | Viewport size | Container size |
| Component reuse | Same everywhere | Adapts to context |
| Use case | Page layout | Component layout |
| Nesting | N/A | Queries nearest container |
When to use each
Container queries:
- Card components
- Sidebar widgets
- Reusable UI components
- Embedded content
- Any component that appears in multiple contexts
Media queries:
- Page-level layout changes
- Navigation bar structure
- Overall grid columns
- Print styles
You can combine both:
/* Page layout with media query */
@media (min-width: 768px) {
.page {
display: grid;
grid-template-columns: 250px 1fr;
}
}
/* Component adapts to its container */
.widget-wrapper {
container-type: inline-size;
}
@container (min-width: 300px) {
.widget {
flex-direction: row;
}
}
Browser support and fallbacks
Container queries are supported in all modern browsers. Provide a default layout for older browsers:
/* Default (works everywhere) */
.card {
display: block;
}
/* Enhanced (container query) */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Older browsers ignore @container and keep the default.
Performance considerations
Container queries add layout calculations. Best practices:
- Keep containers close to the element you are styling
- Avoid deeply nested container queries
- Use
inline-sizeinstead ofsizewhen possible - Test performance with many components on page
Summary
Container queries make components truly reusable:
- Define containers with
container-type: inline-size - Name containers for explicit targeting
- Query containers with
@container (min-width: x) - Use container units like
cqifor relative sizing - Combine with media queries for page-level layout
The MDN container queries guide has the full specification and examples.
