CSS container queries vs media queries
For twenty-plus years, responsive CSS meant one thing: media queries keyed to viewport width. Container queries, now supported in every major browser, solve a different problem that media queries structurally can't — and understanding the difference changes how you build reusable components.
The problem media queries can't solve
A card component might live in a full-width page, a narrow sidebar, or a three-column grid — all on the same page, at the same viewport width. A media query only knows the browser window's size; it has no idea how wide the card's actual container is. So a card designed to switch from a horizontal layout to a stacked layout "when narrow" can't do that with `@media`, because the viewport might be wide even while the card itself is squeezed into a 240px sidebar.
The old workaround was JavaScript — a `ResizeObserver` toggling classes — which works but adds a dependency and a layout-thrash risk for something that is fundamentally a styling concern.
Container queries: the same idea, keyed to an ancestor
Container queries let an element react to the size of a specific ancestor you opt into, called a "containment context":
.card-wrapper {
container-type: inline-size;
container-name: card;
}.card { display: flex; flex-direction: column; }
@container card (min-width: 400px) { .card { flex-direction: row; } } ```
`container-type: inline-size` tells the browser to track the wrapper's inline (horizontal) dimension and establish containment, which is required for the query to work and also has a side effect: the container's children can no longer influence its own size in that axis (this is what makes the query well-defined, since otherwise you'd get a resize loop).
The `container-name` is optional but recommended once you have nested containers, so `@container card (...)` doesn't accidentally match the wrong ancestor.
Container query units
Alongside the at-rule, CSS added units that resolve against the nearest container instead of the viewport: `cqw`, `cqh`, `cqi`, `cqb`, `cqmin`, `cqmax`. A heading that should scale with its card rather than the page:
.card h2 {
font-size: clamp(1rem, 5cqi, 1.75rem);
}`5cqi` is 5% of the container's inline size — same idea as `vw` but scoped to the component, which is exactly what you want for anything meant to be dropped into different layouts.
When to still use media queries
Container queries don't replace media queries; they cover different layers:
- **Page-level layout** (does the sidebar exist at all, how many grid columns, is the nav a hamburger) — this is about the viewport and user's device, so media queries are still correct.
- **Component-level layout** (does this specific card stack or go horizontal, does this specific table become a card list) — this is about the component's own space, so container queries are correct.
A practical rule: if the answer to "how should this look" depends on where the component is placed on the page, use a container query. If it depends on the device or window, use a media query. Many real layouts use both — a media query to decide the number of grid columns, and a container query inside each grid cell to decide how the card in that cell renders.
Browser support and fallback
Container queries reached baseline support across Chrome, Edge, Firefox and Safari in 2023. For projects that must support older browsers, feature-detect with `@supports`:
.card { flex-direction: column; }@supports (container-type: inline-size) { .card-wrapper { container-type: inline-size; } @container (min-width: 400px) { .card { flex-direction: row; } } } ```
Everyone gets the stacked layout by default; capable browsers upgrade to the responsive one.
Style queries (newer, less supported)
A related feature, style container queries, lets you query a custom property value on an ancestor rather than its size:
@container style(--theme: dark) {
.card { background: #111; color: #eee; }
}Support here is newer and patchier (Chrome/Edge first), so treat it as progressive enhancement rather than a primary mechanism today.
Practical migration tip
You don't need to rewrite existing media-query-based layouts. The highest-value place to introduce container queries is any component you already reuse in more than one layout context — a card, a stat widget, a nav item — where you've historically resorted to modifier classes like `.card--narrow` set manually by whichever page happens to embed it. Container queries let the component figure that out itself.