CSS Container Queries: Truly Responsive Components
You build a card component that looks great in a two-column grid. Someone reuses it in a narrow sidebar, and the text overflows because your media query only checks the viewport width — which is still "desktop-sized" even though the component itself is squeezed into 240px. Media queries answer "how wide is the screen," not "how wide is this component," and that mismatch is exactly what container queries fix.
The core idea
Media queries respond to the viewport. Container queries respond to a specific container's size, regardless of where that container sits on the page:
.card-container {
container-type: inline-size;
container-name: card;
}
.card {
display: flex;
flex-direction: column;
}
@container card (min-width: 400px) {
.card {
flex-direction: row;
}
}
Now .card switches to a horizontal layout whenever its container is at least 400px wide — whether that's a full-width hero section or a 400px-wide sidebar panel, it responds the same way, correctly, in both places.
Setting up a container
Any element can become a query container by giving it container-type:
.sidebar {
container-type: inline-size;
}
inline-size queries just the width (the most common case). size queries both width and height, but requires the container to have an explicit size — it won't work on an element that sizes itself based on its content, since that would create a circular dependency.
A practical example: a reusable stat widget
.stat-widget {
container-type: inline-size;
container-name: stat;
}
.stat-widget .value {
font-size: 1.5rem;
}
@container stat (min-width: 300px) {
.stat-widget {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 1rem;
}
.stat-widget .value {
font-size: 2.5rem;
}
}
Drop this widget into a narrow dashboard tile and it stacks vertically with a small number. Drop the exact same component into a wide feature panel and it switches to a horizontal layout with a bigger number — no JavaScript, no separate component variant, no props threading "isCompact" down from a parent that has to know about viewport breakpoints.
Container query units
cqw, cqh, cqi, cqb scale relative to the container instead of the viewport:
@container stat (min-width: 300px) {
.stat-widget .value {
font-size: clamp(1.5rem, 8cqi, 3rem);
}
}
8cqi means "8% of the container's inline size" — the text scales smoothly with the container's actual width rather than jumping between fixed sizes at breakpoints.
Common mistakes
- Forgetting to set
container-typeon the parent — without it,@containerqueries simply don't match anything, and there's no error to tell you why. - Querying a container for its own size from within itself — you must set
container-typeon an ancestor, not the element you're styling; an element can't query its own dimensions. - Using
sizecontainment on an element whose height depends on its content, which creates an impossible circular layout requirement. - Assuming container queries replace media queries entirely — they don't; page-level layout decisions (like your overall grid or nav collapse) are often still better driven by viewport-level media queries, with container queries handling component-level responsiveness within that layout.
What I'd actually use
Container queries for any component meant to be reused in genuinely different layout contexts — cards, widgets, sidebar panels — and media queries for the outer page-level layout (overall grid, nav collapse points). They're complementary, not competing. Browser support is solid across all current major browsers now, so there's little reason to avoid them for new component work.
Next steps
Pick one component in your codebase that looks wrong when reused in a narrower context than it was designed for, and try wrapping its parent in container-type: inline-size with a single @container breakpoint. It's usually a smaller change than the JavaScript-based "isCompact" prop it replaces.