Components that respond to their container — not the viewport
This is the demo most tutorials never build: grab the bottom-right corner of the dashed container and drag. The card switches between three layouts based on the container's width — the viewport never changes.
Adaptive noise cancellation with 40-hour battery. The card layout you're seeing depends entirely on this container's width.
$249One component, zero extra classes. It's stacked in the sidebar and horizontal in the main area — simultaneously.
Same CSS class — expanded layout because the container is wide.
$249Units like cqi work like vw — but relative to the container. Drag this container: everything inside scales proportionally, from padding to font sizes.
This heading is clamp(15px, 6cqi, 34px) — 6% of the container's inline size, clamped. Padding, button, and radius all use cqi too. A truly self-scaling component.
| Unit | Means | Use for |
|---|---|---|
| cqi | 1% of container inline size | ✅ Recommended default — logical, RTL-safe |
| cqb | 1% of container block size | Vertical scaling (needs container-type: size) |
| cqw | 1% of container width | Physical width — prefer cqi instead |
| cqh | 1% of container height | Physical height — prefer cqb instead |
| cqmin | 1% of the smaller axis | Square elements that must never overflow |
| cqmax | 1% of the larger axis | Backgrounds that must always cover |
cqi but no ancestor is declared as a container, the unit silently falls back to the small viewport unit (svi) — your component scales with the viewport instead of the container, and nothing errors. If cq units "aren't working," check that container-type exists somewhere up the tree.
cqi follows the logical inline direction — it automatically flips for vertical writing modes and stays correct in RTL layouts. cqw is physically locked to horizontal width. Same value in most Western layouts, but cqi is the future-proof habit.
Named containers, style queries, and the four traps that make people say "container queries aren't working."
Without a name, @container queries the nearest ancestor container — which breaks when containers nest. Names let inner components target the outer layout container and their own wrapper independently. The container: name / type shorthand sets both at once.
Set a custom property on any ancestor; descendants restyle automatically — no class on every child. Every element is a style container by default (no container-type needed). Support: Chrome, Edge, Safari 18+; Firefox still in development — treat as progressive enhancement.
Queries apply only to a container's descendants — never the container itself (that would create a sizing loop). The universal fix: wrap the component in a dedicated container div.
size containment means the container's height can no longer be derived from its children — with no explicit height it collapses to zero. This is why inline-size is the recommended default: content still grows the height naturally.
Five checks resolve nearly every "not working" report. #4 is the sneaky one during migration — container breakpoint values need rethinking, not copying.
If it styles the page skeleton, use @media. If it styles a component that could live anywhere, use @container. User-preference queries (color scheme, motion) are always media queries — they have no container equivalent.
container-type: scroll-state, queries whether a sticky element is currently stuck, snapped, or scrollable — pure CSS for "shrink the header when it sticks." Shipped in Chrome/Edge (late 2025); guard with @supports (container-type: scroll-state) until Firefox and Safari land it.