container-type: inline-size vs size — Which One You Actually Want
The three container-type values, the containment each applies, and why size collapses elements that size themselves.
Table of contents
container-type: size looks like the thorough choice. It is the one that collapses your element to zero height.
The three values differ in how much containment they apply, and containment is a promise to the browser that it can lay out the element's contents without consulting anything outside it. Make a promise you cannot keep and the layout breaks in a way that looks nothing like a container query problem.
Key takeaways#
inline-sizeapplies inline-axis containment. The container's height still comes from its content. This is what you want almost every time.sizeapplies containment on both axes, so the element no longer sizes itself from its content and needs an explicit height.normalis the default: no size containment, but style queries and named containers still work.- Query containment is not free — a container also gets
layout,styleandpaintcontainment, which creates a new stacking and formatting context. - An element cannot query itself.
container-typegoes on the parent.
The three values#
| Value | Contains | Height from content? | Can query |
|---|---|---|---|
normal | nothing sized | Yes | Style queries only |
inline-size | Inline axis | Yes | width, inline-size |
size | Both axes | No | width, height, block-size, aspect-ratio |
Why does container-type: size collapse my element?#
Because size containment tells the browser to lay out the element without measuring its contents in either direction. The element's own size is then whatever the outside says it is — and if nothing outside sets a height, that is zero.
A div with container-type: size and three paragraphs inside renders as a zero-height box with the text spilling out. Nothing errors, and the container query you wrote never matches, so the natural conclusion is that container queries are broken.
They are not. The element gave up its right to be as tall as its content, which is exactly what you asked for:
/* Collapses — nothing gives it a height. */
.card {
container-type: size;
}
/* Fine — the height comes from outside. */
.card {
container-type: size;
height: 320px;
}inline-size avoids the problem entirely by containing only one axis. The width is constrained and queryable; the height still grows to fit the content, the way a block element normally behaves. That combination is what nearly every component wants.
What containment actually applies#
The container-type values are shorthand for a set of contain values, and the extras are easy to miss:
inline-sizeimpliescontain: layout style inline-sizesizeimpliescontain: layout style size
layout and style come along in both cases, and they have consequences beyond sizing.
Layout containment creates a new containing block for absolutely and fixed-positioned descendants. A tooltip using position: fixed to escape an overflow-hidden ancestor will now be positioned relative to the container instead of the viewport. This is the second-most-common surprise after the height collapse.
It also creates a new stacking context, so z-index values inside the container are resolved among themselves and cannot interleave with elements outside it.
Margins no longer collapse through the container boundary, which can shift vertical rhythm slightly on first adoption.
None of these are bugs, and all of them are the price of the browser being able to answer "how wide is this container?" without a circular dependency. Knowing they exist turns a baffling positioning change into an expected one.
Where does container-type go?#
On the parent of the element you want to style — never on the element itself. An element cannot query its own size, because its size may depend on the styles the query would apply, and the browser refuses to enter that loop.
/* Wrong: .card tries to query itself. Never matches. */
.card {
container-type: inline-size;
}
@container (width > 30rem) {
.card {
grid-template-columns: 10rem 1fr;
}
}
/* Right: the wrapper is the container, the card is styled. */
.card-wrapper {
container-type: inline-size;
}
@container (width > 30rem) {
.card {
grid-template-columns: 10rem 1fr;
}
}The extra wrapper element is the real cost of container queries, and there is no way around it. In practice a component's outermost element usually becomes the container and everything it styles sits one level in.
Naming containers#
With nesting, an unnamed @container query resolves against the nearest ancestor container — often not the one you meant. The container shorthand sets a name and a type together:
.sidebar {
container: sidebar / inline-size;
}
.card-wrapper {
container: card / inline-size;
}
@container card (width > 30rem) {
.card {
grid-template-columns: 10rem 1fr;
}
}Now the query is explicit about which ancestor it means, and dropping the card into a different context cannot silently re-target it. Name containers in any codebase where components nest, which is all of them.
Does normal do anything?#
Yes, and it is easy to overlook. container-type: normal applies no size containment, so the element cannot answer size queries — but it is still a style query container, and it can still carry a name.
That makes it the right value when you want to query a custom property rather than a dimension:
.theme-region {
container: theme / normal;
}
@container theme style(--density: compact) {
.card {
padding: 0.5rem;
}
}No containment, no collapse risk, no new stacking context. For style queries specifically, normal is the correct and cheapest choice.
Choosing, in one pass#
- Do you need to query height or aspect-ratio? If not — and you usually do not — use
inline-size. - If you do, does the element already have a definite height from a grid track, an explicit value, or absolute positioning? Only then is
sizesafe. - Querying a custom property rather than a dimension? Use
normal. - Nested containers anywhere in the codebase? Name them:
container: name / inline-size. - Something positioned oddly after adding a container? Check for
position: fixeddescendants — layout containment moved their containing block.
For the length units that resolve against these containers, see CSS container query units.
If a containment rule is buried in a long stylesheet, the CSS Formatter will surface it with consistent indentation.
Tags
- CSS
- Responsive
- Layout
- Design Systems