Container queries solved the layout half of "this component needs to look different depending on where it's placed" — but the sizing half of that problem was still stuck using viewport units, which don't know anything about the container. Container query units close that gap, and they change how you think about building genuinely reusable components.
What cqw and cqh Actually Measure
cqw is 1% of the query container's inline size (width, in a standard horizontal writing mode). cqh is 1% of the block size (height). There's also cqi and cqb for logical inline/block sizing that respect writing-mode, plus cqmin and cqmax for the smaller/larger of the two — mirroring the vmin/vmax pattern from viewport units. Full unit list is on MDN's CSS container query units page.
The critical setup step people miss: these units only resolve against a container if an ancestor explicitly opts in:
.card-container {
container-type: inline-size;
container-name: card;
}
.card-title {
font-size: 6cqw;
}
Without container-type on an ancestor, cqw doesn't error — it falls back to essentially the small viewport width, which silently produces the wrong result instead of a visible failure. This is the single most common bug when adopting these units: sizing looks fine in isolation during dev, then breaks the moment the component is nested somewhere without a query container ancestor.
Use Case 1: A Card Component That Resizes With Its Grid Slot
The textbook case is a card that appears in a 4-column grid on a dashboard and also as a single full-width card in a sidebar — same component, same markup, and the title, padding, and image aspect ratio should all scale to whatever slot it's dropped into, not to the viewport:
.card {
container-type: inline-size;
}
.card__title {
font-size: clamp(1rem, 5cqw, 1.5rem);
padding: clamp(0.5rem, 3cqw, 1.5rem);
}
In the 4-column grid, the container is narrow, so the title sits near the lower clamp bound. Drop the exact same component into a full-width sidebar slot and the container is wide, so the title scales up toward the upper bound — with zero JavaScript and no per-context CSS override. This is the case viewport units structurally cannot handle, because vw has no idea the card is in a 4-column grid versus a single-column sidebar.
Use Case 2: Typography That Scales With a Sidebar, Not the Page
Product descriptions, comment threads, or any component that can be rendered either in a narrow sidebar or a wide main column benefit from cqi (inline logical size) driven type scale rather than a page-level clamp(1rem, 2vw, 1.25rem), which doesn't know it's sitting in a 280px sidebar and will oversize text there.
.article-body {
container-type: inline-size;
}
.article-body p {
font-size: clamp(0.9375rem, 2cqi, 1.125rem);
line-height: 1.6;
}
Use Case 3: Aspect-Consistent Media in Variable-Width Slots
Thumbnails and preview images inside cards that get placed in variably-sized grid tracks can use cqw to keep padding and overlay text proportional to the actual rendered image width rather than a fixed pixel value that looks right in one grid size and cramped in another:
.media-card {
container-type: inline-size;
}
.media-card__overlay {
padding: 4cqw;
}
.media-card__overlay-title {
font-size: clamp(0.875rem, 4.5cqw, 1.25rem);
}
Decision Point: cqw vs. clamp() with Viewport Units vs. Percentage
| Situation | Best tool |
|---|---|
| Sizing depends on where the component is placed (grid slot, sidebar vs. main) | Container query units (cqw, cqi) |
| Sizing depends on overall page/device size (hero heading, page margins) | Viewport-based clamp() |
| Simple proportional sizing within a fixed, known parent | Percentage (%) — still fine, don't over-engineer |
| Component genuinely reused across drastically different container widths | clamp() combined with container query units, e.g. clamp(1rem, 4cqw, 2rem) |
The overlap case — combining clamp() with a container unit as the flexible middle argument — is usually the best default for any component you expect to be reused in more than one layout context, since it gets container-awareness and hard min/max bounds in one declaration. The Clamp Calculator generates the min/preferred/max syntax; swap the viewport unit in the middle argument for a container unit once you've set container-type on the right ancestor.
Which Container Do Nested cqw Values Resolve Against?
When components nest — a card inside a grid, itself inside a page section — and more than one ancestor sets container-type, cqw resolves against the nearest ancestor query container, not the outermost one. This matters once you start composing reusable components, because a cqw value written for one context can silently pick up a different, closer container if that component gets nested inside another container later:
.section { container-type: inline-size; } /* outer */
.card { container-type: inline-size; } /* inner, nested inside .section */
.card__title { font-size: 5cqw; } /* resolves against .card, not .section */
If you want a value to intentionally skip the nearest container and reference a named ancestor further up, use container-name and the container() targeting available through named containers rather than assuming proximity always gives you the container you meant. This is easy to get wrong in a design system where components get composed inside other components that also happen to set container-type for their own, unrelated reasons — a title tuned in isolation can render differently once nested inside another team's container.
container-type: size vs inline-size
Most of the examples above use container-type: inline-size, which only contains (and queries against) the element's width — height is left to flow normally, which is what you want for typical block-level components. container-type: size contains both axes, which is what makes cqh meaningful, but it also means the container no longer sizes to its content's natural height — you must give it an explicit height (or the layout collapses), which is a much bigger structural commitment than inline-size. Reach for size containment only when you specifically need cqh to do real work (a fixed-height card where content should scale to fill it); default to inline-size for the common case of width-driven, height-auto components.
Where Container Query Units Aren't the Right Fit
Don't reach for cqw on elements without a genuinely reusable, multi-context component — if something only ever renders in one layout position (a fixed sidebar widget that's never moved), a container query adds an indirection layer with no payoff, and a plain fixed value or simple percentage is easier to reason about. Also skip it for page-level typography like a hero <h1> that should track overall viewport size, not a specific ancestor's box — that's still clamp() with vw, not cqw. Browser support for the underlying container query feature is solid across current Chrome, Firefox, and Safari, but verify your minimum supported versions on caniuse.com before removing a viewport-unit fallback in a project with strict legacy browser requirements.