Container Queries (@container)
Container queries let an element respond to the size of its container — a specific ancestor element — instead of the size of the whole browser viewport. This solves a problem @media cannot: styling a genuinely reusable component that might sit in a wide main column on one page and a narrow sidebar on another, with the exact same viewport width in both cases.
@container vs @media
@media queries only ever see the viewport (or device characteristics) — they have no idea how wide the specific element they're styling actually is. A card component using @media to switch to a horizontal layout above 600px will do so even if that card is squeezed into a 300px-wide sidebar on a 1400px-wide screen, because 1400px passes the media query regardless of the card's real size. @container fixes this by querying the element's actual container box instead of the viewport.
Step 1: Establish a Containment Context
Before any descendant can query a container's size, some ancestor must opt in with container-type, and usually also give itself a name with container-name (or the container shorthand) so queries can target it specifically:
.card-slot {
container-type: inline-size; /* size containment along the inline axis */
container-name: card; /* optional, but recommended once you have
more than one kind of container nearby */
}
/* shorthand for both at once: */
.card-slot {
container: card / inline-size;
}inline-size is by far the most common value — it means "this element's width becomes queryable" (in a standard horizontal writing mode). container-type: size also makes the block dimension (height) queryable, but requires the container to have a definite size in that axis, which is a much less common setup.
Step 2: Write the Container Query
.card {
display: grid;
gap: 8px;
}
/* Query the nearest ancestor named "card" that established containment */
@container card (min-width: 400px) {
.card {
grid-template-columns: 120px 1fr; /* switch to a horizontal layout */
align-items: start;
}
}
@container card (min-width: 600px) {
.card {
grid-template-columns: 180px 1fr;
gap: 16px;
}
}Worked Example: A Card That Adapts to Its Slot
<div class="main-column"> <!-- wide slot -->
<div class="card-slot">
<article class="card">
<img src="thumb.jpg" alt="" />
<div>
<h3>Article title</h3>
<p>Summary text…</p>
</div>
</article>
</div>
</div>
<aside class="sidebar"> <!-- narrow slot, same component -->
<div class="card-slot">
<article class="card">
<img src="thumb.jpg" alt="" />
<div>
<h3>Article title</h3>
<p>Summary text…</p>
</div>
</article>
</div>
</aside>The exact same .card markup and CSS automatically renders as a stacked, single-column layout inside the narrow sidebar and a horizontal image-plus-text layout inside the wide main column — without a single JavaScript measurement, and without the component needing to know anything about the page around it. That's the whole point: the component describes its own responsive behavior, wherever it's dropped.
Container Query Length Units
Inside a containment context, you also get container-relative length units — cqw, cqh, cqi, cqb, cqmin, cqmax — the container- query equivalents of viewport units, scaled to the container instead of the viewport:
.card__title {
font-size: clamp(1rem, 5cqi, 1.5rem); /* scales with the container's inline size */
}A container query only ever sees its own established container — not the viewport, and not an unrelated ancestor that never opted in with
container-type.Name containers once you have more than one nested containment context, so queries target the intended ancestor unambiguously.
container-type: inline-sizeis the workhorse value for the vast majority of real component layouts.