A practical guide to 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.
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.
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.
.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.
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.
container-type on the parent — without it, @container queries simply don't match anything, and there's no error to tell you why.container-type on an ancestor, not the element you're styling; an element can't query its own dimensions.size containment on an element whose height depends on its content, which creates an impossible circular layout requirement.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.
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.