Concept
Media queries answer one question: "how wide is the browser viewport?" That's the wrong question for a genuinely reusable component, a card component might render inside a full-width page section (where it should show a large image + horizontal layout) or inside a narrow sidebar widget (where it should stack vertically) on the exact same page, at the exact same viewport width. Viewport media queries have no way to express "respond to your own container's size", Container Queries were built specifically to fill that gap.
Enabling container queries
.card-wrapper {
container-type: inline-size; /* opt this element in as a query container, tracking its inline (width) size */
container-name: card; /* optional, name it for targeted querying, useful with nested containers */
}.card {
display: block;
}
@container card (min-width: 400px) {
.card {
display: flex; /* switches to horizontal layout once its CONTAINER (not viewport) is 400px+ */
}
}An element must explicitly opt in via container-type before its descendants can query its size, this is deliberate: without an explicit opt-in, every element would be a potential query target, which would be both a performance problem and ambiguous (which ancestor's size should a query target by default?).
container-type values
container-type: inline-size; /* track width only, by far the most common use case */
container-type: size; /* track both width and height, requires an explicit, non-auto height on the container */
container-type: normal; /* default, not a query container at all */inline-size is what you want the vast majority of the time, querying height is rarer and requires the container to have a defined height in the first place (an element with height: auto sized by its own content can't sensibly be queried for its own height, since that would create a circular dependency).
Container query units
.card-title {
/* cqw = 1% of the query container's width, analogous to vw but relative to the container, not viewport */
font-size: clamp(1rem, 5cqw, 2rem);
}cqw/cqh/cqi/cqb (container query width/height/inline/block) let you size things fluidly relative to the container rather than the viewport, the container-scoped equivalent of vw/vh, useful for genuinely fluid typography/spacing inside a component that itself might be resized independent of the viewport.
Named containers for disambiguating nested queries
.page-section { container-type: inline-size; container-name: section; }
.sidebar { container-type: inline-size; container-name: sidebar; }
/* Explicit: only responds to the "sidebar" container, not any other ancestor container */
@container sidebar (max-width: 300px) {
.card { display: block; }
}When a component might be nested inside multiple query containers (a card inside a widget inside a page section, each independently sized), naming lets a query target a specific ancestor container rather than just "the nearest one," avoiding ambiguity as nesting gets deeper.
Style queries (newer, less broadly supported)
@container style(--theme: dark) {
.card { background: #111; color: white; }
}Beyond size, container queries are extending to style queries, responding to a custom property's value on the container, not just its dimensions. Support is newer and narrower than size-based container queries; verify current browser support before relying on it as a primary mechanism.
Common Mistakes
1. Forgetting container-type on the ancestor entirely
/* @container rule silently does nothing without an ancestor opted in via container-type */
@container (min-width: 400px) { .card { display: flex; } }Without an ancestor explicitly declaring container-type, the @container rule has no container to query and simply never matches, a common "why isn't this working" moment, since there's no error, just silent non-application.
2. Using container-type: size without a defined height
/* Circular: container needs a height, but height:auto derives from content, which may itself depend on the query */
.wrapper { container-type: size; } /* height: auto by default, likely broken */size containment requires an explicit, non-content-derived height (a fixed height, or a height from a parent Grid/Flex context), using it on a naturally content-sized element creates a circular sizing problem. Default to inline-size unless you specifically need height-based queries and have a defined height source.
3. Querying the wrong ancestor in deeply nested layouts without naming containers
In deeply nested component trees, an unnamed @container query targets the nearest ancestor container, which may not be the one you intended if there are multiple nested query containers, name containers explicitly once nesting gets non-trivial.
4. Trying to query an element's own size from within itself
/* A container cannot query its OWN size, only descendants can query it */
.card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card { display: flex; } /* This does NOT work as expected, .card queries itself */
}The element with container-type set becomes the query context for its descendants, not for itself, querying rules should typically be set up with the container on a wrapper element, and the actual responsive styles applied to a child inside it.
5. Assuming container queries fully replace media queries
Container queries solve component-level, context-independent responsiveness. Page-level concerns that are genuinely about the viewport (like a global navigation layout switch, or prefers-color-scheme/prefers-reduced-motion) are still correctly handled by media queries, the two are complementary, not a strict replacement of one by the other.
Best Practices
- Use
container-type: inline-sizefor the overwhelming majority of cases, width-based queries. - Name containers explicitly once you have more than one level of nested query containers, to avoid ambiguous "nearest ancestor" targeting.
- Set
container-typeon a wrapper, and put the actual@containerrules on its children, not the container element itself. - Reach for container queries specifically for genuinely reusable components (design system cards, widgets placed in variable-width contexts), keep page-level/viewport-driven concerns on regular media queries.
- Use
cqw/container query units for fluid sizing inside a component that should scale with its own container rather than the viewport. - Check current browser support for style queries specifically if considering them, since they're newer and less universally supported than size-based container queries.
Further Resources
- MDN, CSS Container Queries
- web.dev, Container queries
- Ahmad Shadeed, Learn CSS Container Queries
- Una Kravets, CSS Container Queries: A Quick Start Guide
- Can I Use, container queries
