Building component-level responsive design with Container Queries
Starting from the limits of media queries and the old JS-based hacks for watching element size, this post walks through how Container Queries work, how to write @container syntax, and when to reach for it instead of page-level responsive thinking.
The ceiling of media queries: they can only see the whole viewport
Before Container Queries came along, responsive design basically meant @media. But @media only queries the browser viewport's width — it has nothing to do with where a component actually sits on the page or how much space it actually has.
@media (min-width: 768px) {
.card {
display: flex;
}
}This says "switch to a horizontal layout once the screen is wide enough." But if that .card gets dropped into a 300px-wide sidebar, it doesn't matter how wide the screen is — the card still gets forced into a horizontal layout and its content breaks. The root of the problem is simple: a media query asks "how big is the viewport," when what a component actually needs to know is "how much space do I have right now." In a single-column layout those two things usually happen to match. The moment a component gets reused elsewhere, the gap shows up.
Enter Container Queries: let components care about their own container
This is exactly the gap Container Queries close. They let you designate a parent element as a "query container," so its children switch styles based on that container's size instead of the viewport.
Using them takes two steps: declare container-type on the container, then write style rules keyed to that container's size with @container.
/* Step 1: mark .card-wrapper as a query container */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Step 2: switch the card's internal layout based on the container's width */
@container card (min-width: 400px) {
.card {
display: flex;
gap: 16px;
}
}container-type: inline-size is the setting you'll use most — it tracks size only along the inline axis (usually horizontal). container-name gives the container a name so @container can target it precisely, which matters a lot once you have nested containers — without a name, the query just falls back to the nearest ancestor container.
Back to the earlier example: the same .card now automatically switches to a horizontal layout once its parent .card-wrapper is wider than 400px, and falls back to stacking vertically when it's squeezed into a narrow sidebar. CSS handles the entire switch — the component itself never needs to know where on the page it ended up.
Container query units: even your values track the container
Beyond the @container rule itself, CSS also ships a matching set of relative units, so values can scale directly with the container's size without you having to write extra breakpoints.
cqw: 1% of the container's widthcqh: 1% of the container's heightcqi: 1% of the container's inline-axis size (equivalent tocqwin a horizontal writing mode)cqb: 1% of the container's block-axis size
.card-title {
container-type: inline-size;
font-size: clamp(1rem, 4cqi, 1.5rem);
}Combining clamp() with cqi lets the title's font size scale smoothly with the container's width, instead of jumping in steps across a pile of @container breakpoints. This is exactly the effect that used to require JS to compute a ratio and set font-size dynamically — now it's one line of CSS.
When should you actually use Container Queries?
Container Queries aren't here to replace @media — the two operate at different levels:
- Page-level layout decisions (switching to a hamburger menu on mobile, showing or hiding a sidebar) still belong to
@media. These decisions are inherently tied to viewport size. - Component-level style switching (how a card, a form field, or a nav item should lay itself out at different container widths) belongs to
@container— especially for anything that gets dropped into different layout slots as part of a shared component library.
The rule of thumb is simple: if how a component should look depends on where it's placed rather than how big the screen is, that's your cue to reach for Container Queries.
Wrapping up
Making a component adapt to its own container's size used to mean wiring up JavaScript to watch dimensions and toggle classes by hand — logic you had to rewrite for every component, with layout flicker as a common side effect. Container Queries bring this directly into CSS: declare container-type to mark a query container, switch styles by container size with @container, and pair it with relative units like cqw and cqi to scale values smoothly. Just remember one rule of thumb — if a layout decision is about the component's own space rather than the whole viewport, that's when you reach for @container instead of writing another ResizeObserver.