CSS Container Queries: The Complete Practical Guide
For about 15 years, responsive web design has rested on one mechanism: the viewport media query (@media (max-width: 768px)).
Media queries have one blind spot: they only know one thing — how many pixels wide the browser window is.
Here’s where that fails. You design an e-commerce product card that switches from a horizontal layout to a stacked vertical layout below 600px. It works on a phone. But what if a content manager drops that same card into a narrow 300px sidebar on a 4K monitor?
Because the viewport is 3840px wide, your breakpoint never triggers. The card stays horizontal, crushed and overflowing inside the tiny sidebar. It’s broken because it doesn’t know how much space it has — only how much space the browser has.
This is exactly what CSS container queries solve.
As of late 2024, container queries are supported in every major engine (Chrome Blink, Safari WebKit, Firefox Gecko). They’re the biggest shift in frontend layout since CSS Grid.
This guide covers the syntax, the fluid-typography units (CQW/CQI), named container hierarchies, and how to debug the “circular dependency” layout bug.
Minify your CSS. Container queries add new syntax. Keep your production bundles lean by running your stylesheets through our CSS Minifier.
1. The Shift: Component-Level Responsiveness
The syntax difference shows the change in approach:
/* ❌ Legacy: responds to the VIEWPORT */
@media (max-width: 600px) {
.product-card {
flex-direction: column;
}
}
/* ✅ Modern: responds to the PARENT CONTAINER */
@container (max-width: 400px) {
.product-card {
flex-direction: column;
}
}
With container queries, the .product-card becomes self-aware. It doesn’t care whether it’s on an iPhone, an iPad, or a 65-inch TV — only about the size of the element wrapping it.
Drop that card into a full-width grid, a narrow sidebar, or a small modal, and it adapts based on its local surroundings.
2. Step 1: Define the Containment Context
You can’t just write an @container query and expect it to work. You have to tell the browser which parent elements can be measured.
Why? Because layout calculations are expensive. If the browser had to measure every nested element at once, frame rates would drop. You opt in to containment.
.card-wrapper-element {
/* Track this element's horizontal width */
container-type: inline-size;
/* Optional but recommended: name the container to avoid nesting conflicts */
container-name: product-wrapper;
}
The container-type property
There are three values:
inline-size(used 99% of the time): tracks the inline dimension (horizontal width in LTR languages). The height grows and shrinks naturally based on the content inside.size: tracks both width and height. Warning: if you use this, you must give the container an explicit height (height: 500pxoraspect-ratio: 16/9). Otherwise it collapses to0pxand breaks the layout.normal: the default — no containment.
There’s also a shorthand:
.card-wrapper-element {
/* Syntax: container: <container-name> / <container-type> */
container: product-wrapper / inline-size;
}
3. Step 2: Query the Container
Once a parent is a container, you can write queries for the children inside it.
.card {
display: flex;
gap: 20px;
}
/* 1. Default state (assumes there's room) */
.card-image-wrapper { width: 50%; }
.card-data-body { width: 50%; }
/* 2. The narrow state */
/* When 'product-wrapper' shrinks below 400px, restack the layout */
@container product-wrapper (max-width: 400px) {
.card {
flex-direction: column;
}
.card-image-wrapper { width: 100%; }
.card-data-body { width: 100%; }
}
Nested containers
In a DOM tree with multiple nested containers, a generic @container query measures the nearest ancestor with a container-type. That’s unpredictable in complex React trees, which is why container-name matters — naming lets you target a specific ancestor higher up.
@container main-navigation-sidebar (max-width: 300px) {
/* Ignores immediate parents and checks the named ancestor */
.user-profile-avatar {
display: none;
}
}
4. Container Query Units (CQW / CQI)
Viewport media queries gave us vw and vh. Those were great for fluid typography, but they had the same viewport-only blind spot.
Container queries add units relative to the container, not the screen.
| Unit | Meaning | Use case |
|---|---|---|
cqw | 1% of the container’s width | Horizontal fluid scaling. |
cqh | 1% of the container’s height | Vertical fluid scaling. |
cqi | 1% of the container’s inline size | Recommended. Adapts to reading direction (LTR/RTL) automatically. |
cqb | 1% of the container’s block size | Adapts to the block direction. |
cqmin | The smaller of cqi or cqb | Keeping square elements or icons undistorted. |
cqmax | The larger of cqi or cqb | Stretching backgrounds across extreme aspect ratios. |
Fluid, self-aware typography
Combining cqi with clamp() lets you scale type based on the container size, so text doesn’t overflow in small sidebars.
.dashboard-analytics-widget {
container-type: inline-size;
}
.widget-metric-title {
/* clamp(min, fluid, max):
1. Minimum: 1rem (16px)
2. Fluid: 5% of the container width (5cqi)
3. Maximum: 2.5rem (40px) */
font-size: clamp(1rem, 5cqi, 2.5rem);
}
.widget-internal-padding {
/* Padding scales down as the widget shrinks */
padding: clamp(10px, 3cqi, 24px);
}
Drop this widget into a large hero section and the title grows to 40px. Put the same component in a 200px mobile sidebar and it shrinks to 16px — no media queries or JavaScript.
5. Combining CSS Grid and Container Queries
A solid modern pattern uses CSS Grid for the macro layout (the page skeleton) and container queries for the micro layout (the component internals).
/* 1. Macro layout: CSS Grid */
.dashboard-data-grid {
display: grid;
/* Fit as many 300px columns as possible */
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
/* 2. Bridge: make each grid cell a container */
.dashboard-data-grid > div {
container-type: inline-size;
}
/* 3. Micro layout: container queries */
@container (max-width: 350px) {
.financial-stat-card {
/* Stack the icon on top of the number */
flex-direction: column;
text-align: center;
}
}
@container (min-width: 351px) {
.financial-stat-card {
/* Place the icon next to the number */
flex-direction: row;
justify-content: space-between;
}
}
This pattern is robust: Grid handles fitting cells into the window, and container queries handle fitting content inside those cells.
6. Debugging Common Bugs
Bug 1: the circular dependency collapse
The most confusing one: you set container-type: size and your component vanishes.
Cause: you created an impossible loop. The parent asks the children how tall they are so it can shrink-wrap them. At the same time, the children query the parent’s height to decide their own. The browser detects the loop, aborts, and collapses the height to 0px.
Fix: if you must use container-type: size, give the container a fixed height (height: 400px or aspect-ratio: 16/9). Otherwise stick to container-type: inline-size.
Bug 2: trying to query the element itself
You can’t put container-type on an element and then use an @container query to style that same element.
/* ❌ This fails silently */
.action-button {
container-type: inline-size;
}
@container (max-width: 100px) {
/* The button can't query itself! */
.action-button { background-color: red; }
}
An @container query can only style the descendants of the container, never the container itself.
Bug 3: progressive enhancement (the fallback)
Container queries have 95%+ browser support, but regulated apps (banking portals) sometimes have to support old browsers (older Safari on legacy iPads).
Write your base CSS mobile-first, then enhance it with a feature query (@supports).
/* 1. Safe fallback for old browsers */
.complex-card { display: block; }
/* 2. Check if the browser understands container queries */
@supports (container-type: inline-size) {
.card-parent-wrapper { container-type: inline-size; }
/* 3. Apply the modern micro-layout */
@container (min-width: 500px) {
.complex-card { display: flex; }
}
}
Further Reading
- CSS Grid vs Flexbox: The Complete Engineering Guide
- WCAG Color Contrast: Building Accessible Design Systems
- CSS Color System Architectures: RGB, HSL, and OKLCH
- Image Optimization: Handling Responsive
srcsetMath
Stop writing fragile, viewport-dependent media query hacks. Once your fluid typography is in place, run your stylesheets through our CSS Minifier to strip whitespace and comments. Use our CSS Gradient Generator to build backgrounds that scale with cqi units.