Web development
Container queries vs media queries: who decides the layout
Media queries watch the screen, container queries watch the component's space. That decides whether a card adapts anywhere or only at one breakpoint.
Key points
- Media query: a reaction to the viewport. Container query: a reaction to the space the component gets.
- `container-type: inline-size` adds containment on the horizontal axis and can change intrinsic width.
- A container cannot style itself with its own query: you need an inner element.
Media queries measure the viewport; container queries measure the space of the component that contains them. A reusable component wants the second, because the same card lands in a 280 px sidebar and in a 900 px main column.
The same component in two contexts
.card-list {
container-type: inline-size;
container-name: cards;
}
.card {
display: grid;
grid-template-columns: 1fr;
gap: 0.75rem;
}
@container cards (min-width: 30rem) {
.card {
grid-template-columns: 8rem 1fr;
align-items: center;
}
}
The card switches to two columns when its container passes 30rem, not when the screen passes 768 px. In the sidebar it stays single column, in the main column it goes to two: same CSS, no variant.
The same thing with a media query
/* funziona finché la card vive in una sola colonna di pagina */
@media (min-width: 48rem) {
.card {
grid-template-columns: 8rem 1fr;
}
}
/* la variante che serve per la sidebar */
.sidebar .card {
grid-template-columns: 1fr;
}
The media query answers the screen width, so the card widens even when it sits in a 300 px column. The fix is a context selector, and every new context adds another rule.
Who decides what
| Decision | Tool | Why |
|---|---|---|
| Number of page columns | Media query | The page knows the viewport |
| Card with one or two inner columns | Container query | It depends on the space received |
| Sidebar that becomes a drawer | Media query | It is a global navigation decision |
| Text that changes scale | Container query | It depends on the block width |
The record and the command
Blocks built to live in containers of different sizes: uploadthing-image-grid, the card category and the hero sections. The catalogue holds 1,556 blocks and 111,985 components from 1,472 libraries (1 November 2026).
# dove sono dichiarati i container nel progetto
grep -rn "container-type" --include="*.css" src/
# la card deve reggere anche a 320 px di viewport
npx playwright screenshot --viewport-size=320,800 http://localhost:3000 narrow.png
The three limits
- A container cannot style itself: the query applies to descendants, so you need an inner element.
container-type: inline-sizeadds horizontal containment: a child withwidth: 100%no longer widens the container.container-type: sizeneeds an explicit height, otherwise the element collapses.
Known limit: without container-name the query hits the nearest ancestor container, and in a nested layout that container may not be the one you pictured. The explicit name costs one line and removes the ambiguity.
For the grid that hosts the containers see Flexbox or Grid, for shared tracks Subgrid. The same queries as utilities are in container queries with Tailwind.
Frequently asked questions
Do container queries replace media queries?
No. Media queries remain the tool for page structure and navigation choices; container queries serve components that live in different spaces.
Why does the card never change layout?
Almost always the container lacks container-type, or the container is the very element you want to style. The query watches descendants, not the container.
What is the difference between inline-size and size?
inline-size measures the horizontal axis only and needs no height. size measures both axes and needs an explicit height, otherwise the content collapses.
Do container queries cost performance?
The cost is small because the browser recalculates only the container subtree. The risk is containment: a container that no longer grows with its children breaks content-sized layouts.
How do I test them?
Resize the container, not the window: DevTools lets you select an element and read its size. A visual test at 280 px of sidebar covers the case the viewport never shows.