CSS Container Queries: Build Components That Adapt to Their Container, Not the Viewport
Container Queries let UI components respond to their parent's size instead of the viewport. This post shows how to opt in with container-type, a worked card example that flips layout at 350px, and the performance and stacking-context trade-offs you'll hit in production.
31 Mar 2026, 16:48 UTC

The Problem: Viewport Media Queries Break Component Reuse
You’ve built a card component that looks great in an 800 px main column. Drop it into a 300 px sidebar and the layout collapses — text wraps awkwardly, images overflow. The usual fix is a pile of @media (max-width: …) breakpoints tied to the viewport, duplicated across every component. That’s brittle: a design-system card shouldn’t need to know the page’s global breakpoints.
CSS Container Queries (@container) change the contract. A component can now query the size of its nearest ancestor with containment and adapt accordingly. The same card renders as a horizontal row in the main column and a vertical stack in the sidebar — without a single viewport media query.
How It Works: Opt-In Containment
Container queries only work on elements that declare container-type. The most common and performant choice is inline-size, which queries the inline dimension (width in horizontal writing modes) and establishes layout containment on that axis. size queries both dimensions but forces full layout containment, triggering more relayouts — avoid it unless you genuinely need block-dimension queries.
.card-container { container-type: inline-size; }Once the parent opts in, any descendant can use @container (min-width: 350px) { … } with the same condition syntax as @media. The query resolves against that specific container, not the viewport. Nesting works naturally: a child with its own container-type creates a new query context, so inner components query their immediate wrapper — essential for design-system components dropped into unpredictable layouts.
Worked Example: A Card That Flips at 350 px
Consider a product card with an image, title, and description. In wide containers we want image left, text right; in narrow containers we stack vertically.
<div class="card-wrapper"> <article class="card"> <img src="…" alt="" class="card__image"> <div class="card__content"> <h3 class="card__title">Product Name</h3> <p class="card__desc">Description…</p> </div> </article></div>.card-wrapper { container-type: inline-size; }.card { display: flex; flex-direction: column; gap: 1rem; }@container (min-width: 350px) { .card { flex-direction: row; align-items: flex-start; } .card__image { width: 120px; flex-shrink: 0; }}Drop .card-wrapper into a sidebar (width: 300px) and the card stacks. Place the same wrapper in a main column (width: 800px) and it becomes a horizontal row. No viewport breakpoints, no duplication.
Container query units (cqw, cqi, cqmin, …) let fluid typography scale with the container: font-size: clamp(1rem, 2.5cqi, 1.5rem) adjusts heading size relative to the card’s actual width.
Trade-Offs and Gotchas
- Performance:
container-type: sizeforces layout containment on both axes and can cause more relayouts. Default toinline-size. - Stacking context: An element with
container-typebecomes a containing block for absolute/fixed descendants and establishes a new stacking context —z-indexand positioning logic may shift unexpectedly. - Style queries:
@container style(--theme: dark)enables state-based styling (theme variants, disabled states) but browser support is newer (Chrome 111+, Firefox 113+, Safari 16.4+). Verify your baseline before relying on them. - Padding/border: The queried size is the container’s content-box. Padding and border on the container reduce space for children but don’t change the reported query size.
Try It Today: A Five-Minute Verification
- Create a test page with a resizable sidebar:
<aside style="resize: horizontal; overflow: auto; width: 300px">containing the card wrapper above. - Drag the sidebar handle and watch the card flip at 350 px.
- In Chrome DevTools, select the
.card-wrapperelement — the Styles pane shows a container-type badge. Toggle it to see@containerrules activate/deactivate in real time. - Run
npx browserslist --versionsin your project root (requires Node) to confirm your target baseline includes Chrome 105+, Firefox 110+, Safari 16+, Edge 105+ for unprefixed support. - Progressively enhance with
@supports (container-type: inline-size) { … }and test fallback rendering in an older browser (e.g., Safari 15 via simulator).
If the card adapts as you resize the sidebar, container queries are working. If not, the most common culprit is a missing container-type on the parent — the element must opt in.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.