Topic 9.6
Container Queries
In one line
Container queries let a component respond to the size of its own container instead of the whole window: mark an ancestor with container-type: inline-size, then write @container (min-width: 400px) { ... }, and size things with container units like cqi.
Think of it like this
Packing a suitcase. You don't pack based on the size of your house; you pack based on the size of the bag in front of you. In a big suitcase, shirts lie flat side by side; in a small backpack, you roll them up and stack them. A card on a web page is the same: it should arrange itself based on the space it was given (its container), not on the size of the whole browser window.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Container query
- An
@containerrule that applies when an ancestor container matches a size condition. - Query container
- An element marked with
container-type, which its descendants can query. - `container-type`
inline-size(query width),size(width and height) ornormal(not a size container).- `container-name`
- A name for a container, so
@container name (...)can target it specifically. - Inline size
- The size along the text direction; the width in normal horizontal writing.
- Containment
- A promise to the browser that an element's size doesn't depend on its contents, so it can measure it first.
- `cqi` / `cqw`
- Container query units: 1% of the container's inline size / width.
- Style query
@container style(--x: y): a query on a custom property's value instead of a size.
Step by step
01Mark the container
Wrap the component and make the wrapper a container. The wrapper is a normal block, so it takes its parent's width: safe for containment.
<div class="card-slot">
<article class="card">
<img src="chai.jpg" alt="A cup of masala chai" width="400" height="300">
<div class="body">
<h3>Masala chai</h3>
<p>Tea, milk and warm spices.</p>
</div>
</article>
</div>02Write the narrow layout first
Like mobile first, the base is the small version: image on top, text below.
.card-slot {
container-type: inline-size;
}
.card {
display: grid;
gap: 0.75rem;
}
.card img {
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
object-fit: cover;
}03Add the wide layout with @container
When the slot is at least 400px wide, put the image beside the text. The card does this in a sidebar, in a grid cell or full width: it only cares about its slot.
@container (width >= 400px) {
.card {
grid-template-columns: 40% 1fr;
align-items: center;
}
}04Name containers when they nest
A query looks at the nearest container ancestor. If containers nest (a page layout container around a card container), name them so each query asks the right one.
.sidebar { container: sidebar / inline-size; }
.card-slot { container: card / inline-size; }
@container sidebar (width < 250px) {
.sidebar .promo { display: none; }
}
@container card (width >= 400px) {
.card { grid-template-columns: 40% 1fr; }
}05Scale text with the container
Container units make type follow the component. Here the title grows with the card's width.
.card h3 {
font-size: clamp(1.1rem, 0.8rem + 2cqi, 1.6rem);
}06Media query vs container query
The same window, two answers: a media query sees one viewport; container queries see each slot.
Try it yourself
- 1
Move the breakpoint
In the first example, change
300pxin the@containerrule to450px. Predict both console lines before you run. (Both becomecolumn, because the wide slot is only 420px.) - 2
Try a media query instead
In the first example, change
@container (width >= 300px)to@media (width >= 300px). Predict: do the two cards still differ? (No: both see the same viewport.) - 3
Fix the trap
In the last example, remove
display: inline-blockfrom.tag. Predict whether the container still collapses. (No: a block takes its parent's width, which doesn't depend on its content.)
Code & diagrams
The same HTML and CSS, at the same window width, gives two layouts, because each card asks its own slot. The wide slot may stick out of the narrow preview; scroll sideways to see it.
Preview
Console
Nothing logged yet.Expected console output
slot narrow: column
slot wide: rowThe script tries three widths and reads the result, then hands the box back to you: drag its bottom-right corner to resize it yourself. The label and its size change with the box, not the window. The font size is 0.5rem + 4% of the box's content width (8px + 7.2px at 180px), capped at 1.6rem (25.6px).
Preview
Console
Nothing logged yet.Expected console output
180px: "small panel" 15.2px
300px: "medium panel" 20px
500px: "large panel" 25.6pxAn `inline-block` normally takes the width of its text. With inline-size containment its width is calculated as if it were empty, so only its padding and border remain (18px) and the text spills out. Make the parent the container instead.
Preview
Console
Nothing logged yet.Expected console output
normal: has width
container: collapsed to 18pxBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Querying the container itself
Make .card the container (container-type: inline-size) and write @container (width >= 400px) { .card { flex-direction: row; } }.
Break #2
A flex item container that collapses
In a display: flex row, give one child container-type: inline-size and no flex-basis or width.
Myth vs fact
Myth
Container queries replace media queries.
Fact
They complement them. Page layout and user preferences (dark mode, reduced motion) still use media queries; components use container queries.
Myth
Every element can be queried by its children.
Fact
Only elements with container-type set to inline-size or size are size containers. Without one, @container queries never match.
Myth
container-type: size is the more complete choice, so use it.
Fact
size contains the height too, so a container whose height comes from its content collapses to zero height. Use inline-size unless you really need to query height and the container has a set height.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Size containment is what makes container queries possible without layout loops: the browser lays out the container's size first, then evaluates
@containerrules for descendants, then lays out the inside.container-type: inline-sizealso applies layout and style containment, so the container becomes an independent formatting context (floats and child margins stay inside it) and CSS counters inside it are scoped to it. - ▸
Browser support: Chrome/Edge 105 (2022), Safari 16 (2022) and Firefox 110 (February 2023), making size queries Baseline 2023. Container units arrived at the same time. Style queries for custom properties shipped in Chromium (2023) and Safari (2024); check current Firefox support before using them.
- ▸
The
@containerprelude acceptsand,orandnot, like media queries:@container card (width >= 400px) and (height >= 300px)(height needscontainer-type: size). There's also anorientationandaspect-ratiofeature for size containers. - ▸
Design systems benefit most: a component can ship with its own responsive rules and no knowledge of the page grid. Combine with
cqi-based fluid type for components that scale smoothly in any slot.
Remember this
- 1
Media queries (Topic 9.2) only know the viewport. But the same card component might appear in a wide main column, a narrow sidebar and a three-column grid on the same page, at the same window width. A media query gives all three the same answer. A container query asks about a container: an ancestor element you have marked as one. The card then adapts wherever you put it. This is what makes components truly reusable.
- 2
Step one: make an element a query container with
container-type.inline-size(the usual choice) lets children query its inline size, which is its width in horizontal text.sizelets them query both width and height, but then the container's height can't come from its content, so you must give it a height.normal(the default) is not a size container. You can name it withcontainer-name: card, or use the shorthandcontainer: card / inline-size. - 3
Step two: write the query.
@container (min-width: 400px) { .card { grid-template-columns: 120px 1fr; } }applies when the nearest ancestor container is at least 400px wide. Range syntax works too:@container (width >= 400px). With a name,@container card (width >= 400px)skips other containers and asks the nearest one calledcard. A key rule: an element can't query itself. The query always looks at an ancestor, so you style the children of the container, often with a wrapper element as the container. - 4
Why the setup step? To answer "how wide is the container", its width must not depend on its children, otherwise a child changing layout could change the container's width, which would change the query answer, which would change the layout again: an infinite loop.
container-type: inline-sizeapplies inline-size containment: the container's width is calculated as if it had no content. In a normal block that's fine, because blocks take the width of their parent anyway. But a container that shrinks to fit its content (a float, aninline-block, a flex item with no set size, afit-contentbox) collapses to zero width. That is the most common container query bug. - 5
Container query units size things relative to the query container:
cqw(1% of its width),cqh(1% of its height),cqi(1% of its inline size),cqb(1% of its block size),cqminandcqmax(the smaller or larger of the two).font-size: clamp(1rem, 0.5rem + 3cqi, 2rem)gives a heading that scales with its card, not the window. If no eligible container exists, the units fall back to the small viewport units (svw,svh…). - 6
Container queries became Baseline in 2023 (Chrome and Edge 105, Safari 16, Firefox 110). They don't replace media queries: use media queries for page-level decisions ("show the sidebar on wide screens") and container queries for components ("this card goes horizontal when it has room"). Newer style queries (
@container style(--theme: dark)) test custom property values on the container; check browser support before relying on them, as Firefox support arrived later than the size queries.
Explain it without notes
What problem do container queries solve that media queries can't?
What does container-type: inline-size do, and why is it required?
Why can a container collapse to zero width, and how do you avoid it?
What are container query units, and what do they fall back to?
Practice
Make a .profile component stack its avatar above its text by default and place them side by side when its container is at least 360px wide.
Inside a container named widget, hide .extra when the widget is narrower than 250px. Make sure a nested container doesn't answer instead.
Make a card title scale with the card width between 1rem and 1.75rem.
Trade-offs
- ↔
Container queries make components portable, but each container adds containment, which can cause collapsing bugs and needs an extra wrapper element.
- ↔
Container units scale nicely with components but don't respond to zoom the way
remdoes; keep arempart inclamp()as withvw.
Done when you can
I can create a container with
container-typeor thecontainershorthand.I can write
@containerqueries, named and unnamed, with range syntax.I know an element can't query itself.
I can recognise and fix a container collapsing to zero width.
I can use
cqiand other container units.I can choose between a media query and a container query.