I spent years writing a particular kind of JavaScript: a ResizeObserver watching a sidebar's width, toggling a class so a card component could lay itself out differently when squeezed into a narrow column versus a full-width section. It worked, but it was the kind of code that made every redesign slightly scarier than it needed to be. Container queries replaced almost all of it.
The problem with viewport-only breakpoints
Media queries respond to the viewport. That's fine when a component only ever appears in one context, but most real design systems reuse the same card, panel, or widget in a sidebar, a main column, and a modal, all at different widths, often at the same viewport size. A media query has no way to know which context it's in — only the component's own container does.
Container query basics
Container queries flip the question from "how wide is the screen" to "how wide is the box I'm actually in." Setting it up takes two steps: mark a container, then query against it.
Defining a containment context
.card-slot {
container-type: inline-size;
container-name: card-slot;
}
Querying that container
@container card-slot (min-width: 480px) {
.card {
grid-template-columns: 120px 1fr;
}
}
Anything inside .card-slot can now respond to that container's width, regardless of the viewport. Drop the same card markup into a 300px sidebar or a 900px main column, and it lays out correctly in both without a single line of JavaScript or a layout-specific class.
A real example: a card that adapts to its slot
This is close to a pattern I use across most client dashboards now — a stat card that goes from stacked to side-by-side once it has enough room:
.card {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
@container card-slot (min-width: 380px) {
.card {
grid-template-columns: auto 1fr;
align-items: center;
}
.card .icon {
grid-row: span 2;
}
}
The same component file ships everywhere. The layout logic lives in CSS, next to the styles it affects, instead of in a separate JS module tracking breakpoints that have to be kept in sync by hand.
Container query length units
Alongside the @container rule, CSS added container-relative units — cqw, cqh, cqi, cqb — which work like viewport units (vw, vh) but relative to the nearest container instead of the screen. They're particularly good for typography that should scale with its container rather than the page:
.card h3 {
font-size: clamp(16px, 5cqw, 22px);
}
A heading that scales smoothly as its container resizes, without a single resize listener.
Where this still needs a fallback
Browser support has been solid in all major engines since 2023, so at this point I use container queries by default rather than as a progressive enhancement. The one place I still reach for a media query is page-level layout — the overall grid of sidebar plus main content — where there genuinely is no meaningful "container" other than the viewport itself. Container queries solve the component problem; they were never meant to replace page layout media queries entirely.
A useful mental model: media queries answer "what kind of screen is this." Container queries answer "how much room does this specific component have." Most layout bugs come from using one where the question actually called for the other.