Section 2 · Layout That Holds Up
Container Queries
20 min3 min readCode playground
Lesson at a glance
Make a session card change its layout based on the width of the slot it sits in, using size container queries, container units and custom-property style queries.
- 1Make a container, then query it
- 2The two rules that trip everyone
- 3Container units
- 4Style queries for variants
- 5Your turn
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Waypoint uses the same session card in three places: the main schedule grid, a narrow “Up next” sidebar, and a full-width hero for the keynote. A media query can only ask “how wide is the screen?”, and the screen is the same width in all three cases. So the card in the sidebar got the wide layout, squashed into 280px, until someone added a .sidebar .session-card override, then a .hero .session-card override, and so on.
The card shouldn’t care where it is. It should care how much room it has. Container queries let it ask exactly that.
Make a container, then query it
Two steps. First, declare which element is the container, the slot the card sits in:
.slot {
container: session / inline-size;
}That shorthand sets container-name: session and container-type: inline-size. The type tells the browser you’ll query this element’s inline size (its width in horizontal writing), so it must be able to calculate that width without looking at the content inside.
Second, write the card’s layout as a query against that container:
.session-card {
display: block;
}
@container session (width >= 400px) {
.session-card {
display: grid;
grid-template-columns: 6rem 1fr;
gap: 1rem;
}
}Now the card stacks in the 280px sidebar and switches to two columns in the 640px grid, with no knowledge of either. The name is optional; an unnamed @container (width >= 400px) uses the nearest ancestor with a size container type. Names help when containers nest and you want to skip one.
The two rules that trip everyone
An element can’t query itself. The query looks for an ancestor container, so container-type on .session-card plus @container { .session-card { … } } does nothing for the card itself. Put the container on a wrapper.
Containment changes how the container is sized. inline-size containment means the container’s width can’t depend on its children; otherwise a query could change the content, which changes the width, which changes the query, forever.
Container units
Inside a container, you get units relative to it: cqi is 1% of the container’s inline size, cqb 1% of its block size, and cqmin/cqmax the smaller or larger of the two. They make a component scale smoothly, not just at breakpoints:
.session-card h3 {
font-size: clamp(1rem, 0.75rem + 2cqi, 1.5rem);
}In the 280px sidebar the title sits near 1rem; in a 640px slot it grows towards 1.5rem. If no ancestor is a size container, container units fall back to the small viewport units, so they never break.
Style queries for variants
Size isn’t the only thing a card might respond to. The keynote card in the hero is a variant, and container style queries let a parent announce that through a custom property:
.hero { --variant: featured; }
@container style(--variant: featured) {
.session-card {
border-inline-start: 6px solid #6d28d9;
}
}You don’t need container-type for this: every element is a style container by default, so the query checks the nearest ancestor’s computed --variant. Today, style queries work with custom properties only; querying regular properties like style(display: grid) isn’t supported anywhere yet.
Your turn
In the exercise, the card uses a media query, so it gets the wrong layout in at least one slot. Turn .slot into a named container, switch the layout to a container query at 400px, and add a style query for the featured variant. The checks change the slot’s width and set --variant on it to watch the card respond. That closes the layout section; next you’ll scale type and spacing fluidly across all those slot sizes.
Key takeaways
container-type: inline-sizeturns an element into a container, and@container (width >= 400px)styles its descendants based on that element’s width, not the viewport’s.- An element can’t query itself; put
container-typeon a wrapper and write the query for the elements inside it. - Inline-size containment means the container’s width can’t come from its content, so a container in a shrink-to-fit context collapses unless something gives it a width.
- Container units such as
cqi(1% of the container’s inline size) let type and spacing scale with the component. - Style queries on custom properties, such as
@container style(--variant: featured), became Baseline in May 2026; size queries have been Baseline since 2023.