Container Queries
Date: 2026-09-27
A component styles itself by the space it’s actually given, not by the size of the window. So one card works in a sidebar, a grid and a full-width row. The price is that the container’s size can’t depend on its content, and that constraint explains every “my container collapsed to zero”.
A container query applies styles based on the size of an ancestor that has been declared a container, rather than on the viewport as a media query does. It’s the mechanism that makes a component responsive to its own context — Viewport and Responsive Behaviour covers why viewport breakpoints were the wrong question for components.
The shape
MEDIA QUERY: the viewport is 1200px wide
┌──────── viewport 1200 ─────────────────────────────┐
│ ┌─ sidebar 300 ─┐ ┌─ main 900 ───────────────────┐ │
│ │ [card: WIDE] │ │ [card: WIDE] [card: WIDE] │ │
│ │ ← squashed │ │ │ │
│ └───────────────┘ └──────────────────────────────┘ │
└────────────────────────────────────────────────────┘
CONTAINER QUERY: each card asks its own container
│ ┌─ sidebar 300 ─┐ ┌─ main 900 ───────────────────┐ │
│ │ [card:STACKED]│ │ [card: WIDE] [card: WIDE] │ │
│ └───────────────┘ └──────────────────────────────┘ │
The code
Two parts: declare a container, then query it from inside.
/* 1. the wrapper becomes a container. inline-size = query its width */
.card-slot {
container-type: inline-size;
container-name: card; /* optional; unnamed queries hit the nearest container */
}
/* 2. the card changes shape when ITS SLOT is wide enough */
.card { display: grid; gap: 1rem; }
@container card (min-width: 420px) {
.card { grid-template-columns: 160px 1fr; } /* image beside text */
}
/* container query units: 1cqi = 1% of the container's inline size */
.card h3 { font-size: clamp(1rem, 4cqi, 1.5rem); }An element can’t query itself — only an ancestor container. That’s why the query sits on a wrapper (.card-slot), or on a component’s outer element with the styles applied to its children.
Why the container can’t size to its content
container-type: inline-size applies size containment on the inline axis: the container’s width is worked out as if it had no children. Without that there’d be a loop — the query changes the children, the children change the container’s size, and that changes the query.
The consequence:
container in a block context width comes from the parent → fine
container as a flex item, flex: 1 width comes from free space → fine
container with width: fit-content width comes from content → collapses to 0
container that's a float or same problem
inline-block with no width
If a container collapses to zero or its content overflows, it’s in a shrink-to-fit context. Give it a width from outside (a grid track, flex: 1, width: 100%).
container-type: size contains both axes, so the height needs to come from outside too. It’s rarely what you want.
Media queries still have a job
| Container query | Media query |
|---|---|
| Component layout: card, product tile, promo block | Page layout: how many columns the page has |
| Anything reused in more than one slot | User preferences: prefers-reduced-motion, prefers-color-scheme |
Device capability: hover, pointer: coarse |
User preferences and input type only exist on the viewport. No container knows whether the user has a mouse.
Style queries — @container style(--variant: compact) — query a custom property’s value rather than a size, which lets a parent switch a child’s variant without a class [CHECK: current cross-browser support for style queries; size queries are the widely supported part].
Failure modes
- Zero-width containers, above
- Querying a container that isn’t there. An unnamed
@containerresolves to the nearest ancestor container. Move the component and it silently binds to a different, bigger one. Name containers that components depend on - Every element a container. Containment has a layout cost and breaks shrink-to-fit sizing. Declare containers on slots, not on everything
- Duplicated breakpoints. A component with both media and container queries for the same change will disagree in some slot
Supported in all current major browsers for size queries [CHECK: confirm against current Baseline data if an older-browser audience matters] — Browser Compatibility.
Where it connects
- Responsive Design — container queries are one of the tools that make breakpoints a last resort
- Grid —
auto-fithandles a lot of the same cases without any query - Component API Design — a component that adapts to its container needs fewer size props