A Practical Guide to Container Queries: What Shipped, What Has Not, and the Fallback
Write The Fallback First
The container queries production fallback pattern separates a component that survives a browser without size container queries from one that breaks. Size container queries shipped in Chrome 105, Firefox 110, and Safari 16.0, and are Baseline widely available since 2024-02-14. Style queries shipped later, Chrome 111, Firefox 133, Safari 17.0, and reached widely available on 2025-03-05. The fallback is not a polyfill. It is a media query with a matching breakpoint, or a flexbox wrapping pattern that reflows without the query. Write the non-container styles first. Then gate the container query code behind @supports (container-type: inline-size). The @supports test is what makes the whole thing production-safe, because it tells you exactly when the advanced code will run and when it will not.
What Ships And What Does Not
Before you write any @container rule, know what ships. Size container queries, the ones that respond to a container element's dimensions, are the mature part of the spec. They require a container-type declaration (inline-size or size) and a container-name if you query a specific container. The container-type: inline-size value is the workhorse; it establishes a query container without forcing the element to be fully contained. Firefox shipped in version 110 on 2023-02-14, Safari in 16.0 on 2022-09-12. Interop is solid across current engines, which is why Baseline named it widely available in early 2024. The one real gap in that coverage is users on older device-locked browsers, iOS Safari on unsupported iPhones and Android WebView in apps that do not update, where the container query does not run. That is the population the fallback is for. Write the non-container styles first.
Set Up A Query Container
This is the working container query you can ship today, with the assumption that the fallback is in place. The container-type: inline-size declaration on the parent creates the containment that the query measures. The @container rule then applies styles only when the container is at least 30rem wide. The @container rule cannot style the container element itself; it can only style descendants. That is a spec rule, and violating it silently fails, no warning, no error, the styles do not apply. The container-name is optional if you have only one query container in the tree. If you have nested containers, name them to avoid the wrong one answering. The container shorthand is <name> / <type>, but keep the longhand when you are teaching the pattern.
.card-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
}
.card {
container-type: inline-size;
container-name: card;
}
.card__body {
font-size: 1rem;
padding: 1rem;
}
@container card (min-width: 30rem) {
.card__body {
font-size: 1.25rem;
padding: 2rem;
}
.card__media {
aspect-ratio: 16 / 9;
}
}
This is a complete, runnable sample. The card is the query container; the card body and media respond to the container's width, not the viewport. If the browser does not support container queries, the .card__body keeps its base font-size and padding, and the .card__media gets no aspect-ratio. The grid still lays out the cards, but they reflow based on the viewport, not on each card's own size.
Size Queries Are The Baseline
Size container queries are the Baseline widely available feature. They have been in every modern engine since early 2024, and the syntax is stable. The container-type property accepts inline-size, size, or normal. The default is normal, which means no containment and no query container. If you forget to set it, the @container rule has nothing to measure and silently does nothing. The cqw and cqh units are the container-relative equivalents of vw and vh; 1cqw is 1% of the query container's inline size, and 1cqh is 1% of its block size. There are also cqi (inline), cqb (block), cqmin (smaller of the two), and cqmax (larger). These units are as useful as the queries themselves, but they share an interop bug that affects older Safari versions that shipped container queries: those versions support @container but mishandle the units in certain calc() expressions. If you are targeting Safari 16 or 17.0, verify any cqw or cqh usage in a calc, or stick to the query alone.
Guard With @supports
The container queries production fallback pattern is a two-step process. First, write the base styles that work everywhere. Second, wrap the @container block in an @supports test. The test tells you whether the engine can parse and apply the container query, and it prevents the engine from even trying to parse the @container rule if it would fail. The test is @supports (container-type: inline-size). This is a property:value pair test, the most reliable form of feature detection for container queries. It does not test for @container syntax; it tests for the property that makes the query possible. If the engine supports container-type, it supports @container. If it does not, the @supports block is ignored wholesale, and the engine uses the base styles.
.card-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
}
.card__body {
font-size: 1rem;
padding: 1rem;
}
@supports (container-type: inline-size) {
.card {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 30rem) {
.card__body {
font-size: 1.25rem;
padding: 2rem;
}
}
}
This is the fallback pattern. The base styles sit outside the @supports block, so they apply everywhere. The container query is inside. If the engine cannot handle container-type, it ignores the whole block and uses the base styles. The grid reflows based on the viewport, which is not the same as reflowing based on the card, but it is a working layout. For many components, a viewport-based breakpoint is good enough. If you need the container-relative reflow specifically, for a component that appears in a sidebar and a main column, the flexbox wrapping pattern is the alternative: give the card a flex-basis that wraps when the available space shrinks, without any measurement of the container itself.
Style Queries: Newer, Costlier
Style queries are the other half of the container queries spec, and they are not in the same place. A size query responds to a measurement; a style query responds to the computed value of a custom property on the container. The syntax is @container style(--variant: compact). Chrome shipped in version 111 on 2023-03-07, Firefox in 133 on 2024-11-26, with Safari 17.0 on 2023-09-18. Baseline named style queries widely available on 2025-03-05, which means they are now in the same category as size queries. The practical difference matters: style queries let you switch a component's variant based on a custom property value, which is a constraint-solving system when combined with calc(). You can emulate a mini state machine with custom properties and style queries, but you have to respect the limits, style queries cannot animate, cannot respond to JavaScript state without flipping a custom property, and cannot sequence complex timelines. The performance cost of style queries is higher than size queries because the engine has to evaluate the computed value of the custom property on the container, which may trigger style recalc on the whole subtree.
@supports (container-type: style) {
.card {
container-type: style;
--variant: compact;
}
@container style(--variant: compact) {
.card__title {
font-size: 1rem;
}
}
@container style(--variant: expanded) {
.card__title {
font-size: 1.5rem;
}
}
}
This style query sample is correct in syntax, but it is not yet a universal fallback. The @supports (container-type: style) test is the right guard, but it does not guarantee interop in every engine that passes the test. Older Safari versions that shipped size queries have known interop bugs in their style query implementation, and the Firefox 133 implementation has edge cases with custom property inheritance. The warning is explicit: test style queries in every engine you support. Do not rely on them for critical layout. Use them for typographic or spacing variants where a failure is cosmetic, not structural. The style query cannot be polyfilled. If you need the variant to work everywhere, do it with a class toggle in JavaScript instead.
Containment Has A Price
When you ship a container query, you are shipping more than a rule, you are declaring containment. The container-type: inline-size property forces layout containment on the element, which means the engine knows the element's size is independent of its contents for the purposes of the query. That containment has a cost: it can change how the engine computes the size of the element's contents, and in some cases it can break percentage-based sizing of children. The layout containment is what makes the query possible, but it is not free. The other cost is in the query evaluation itself. Every @container rule that matches the container is evaluated when the container's size changes, which can be frequent during a resize or when content above the container changes. Keep the number of containers low and the query conditions simple. A query on min-width or max-width is cheap; a query that chains multiple conditions or uses complex calc() is not. Measure, do not assume. Use the performance profiler in DevTools to see if the container query is a hot spot. If it is, move the query to a higher-level container so fewer of them re-evaluate.
Container Units And Their Bugs
The container query length units, cqw, cqh, cqi, cqb, cqmin, cqmax, are the units you use inside a @container block to size elements relative to the container. One cqw is one percent of the query container's inline size; one cqh is one percent of its block size. They are the container-relative equivalents of viewport units, and they are as useful for typography and spacing that scale with the container. But they have a specific interop quirk: Safari 16.0, 16.1, and 16.2 shipped container queries but mishandled these units in certain contexts, particularly inside calc() or when the container has a percentage-based size. The result is that a font-size: calc(1rem + 1cqw) could be computed incorrectly, producing a font that is too large or too small. The fix is to avoid using cqw and cqh in calc() when you need to target those Safari versions, or to use a media query fallback for the typographic scale. The units are part of the size query feature, so they are widely available with the same Baseline date, but the word "widely available" hides these version-specific bugs. Test in the oldest Safari you support before trusting the units.
What The Gate Cannot See
The @supports test is the gatekeeper for container queries, but it has limits. It can test property:value pairs, like container-type: inline-size, and it can test selector() functions. It cannot test for every feature or every combination. It cannot test for a bug. An engine can pass the @supports test for container-type and still have a broken implementation of cqw units in a specific situation. The @supports test is necessary, but it is not sufficient for production safety. That is why the fallback pattern writes the base styles first: if the container query code is buggy, the base styles are still there. The other limit is that @supports does not cascade the way other at-rules do. It does not create a new stacking context, and it does not affect specificity. It is a pure feature gate. For the fallback, this is exactly what you want, a binary switch that says yes or no to the whole block. Use it liberally, but do not assume it makes the code safe.
The Gaps The Fallback Does Not Close
The fallback pattern is not a silver bullet. It covers the gap between engines that support size container queries and those that do not, but it does not cover the gap between size queries and style queries. If you are using style queries and the engine does not support them, the @supports (container-type: style) guard will prevent the code from running, but the base styles will not be a good substitute, they will be the non-variant styles, which may be wrong for the intent. The other gap is the container units. The units are part of the size query feature, but they have their own interop bugs, as noted. A fallback that writes base styles in rem or px will not scale with the container, but it will be predictable. The final gap is the failure mode. If the @supports test passes but the container query does not respond, the likely cause is a missing container-type declaration on the container element. The query has no container to measure. This is the most common mistake, and it fails silently, no warning, no error, the style does not apply. To debug, check the computed styles of the ancestor element and confirm that container-type is set to inline-size or size.
FAQ
What is the difference between size container queries and style queries?
Size queries respond to the dimensions of a container element, using min-width and max-width conditions. Style queries respond to the computed value of a custom property on the container, using the style() syntax. The distinguishing feature is the type of condition: a measurement versus a value.
Can I use container queries in production today?
Yes, size container queries are Baseline widely available since February 2024. Style queries are widely available since March 2025. The production pattern is to write base styles, then gate the container query code behind @supports (container-type: inline-size).
What is the fallback if the container query does not run?
The fallback is a media query with a matching breakpoint, or a flexbox wrapping pattern that achieves a similar reflow. Write the non-container styles outside the @container block, then override inside the @container block. The @supports test ensures the override only runs when the feature is supported.
Why is my container query not responding?
The most common cause is forgetting to declare container-type on the ancestor element. Without it, the @container rule has no container to measure, and the query silently does nothing. Another cause is attempting to style the container element itself, container queries can only style descendants.
Are container query length units safe to use?
The units (cqw, cqh, etc.) are widely available, but older Safari versions (16.0-16.2) have interop bugs with them in calc() expressions. Test in the oldest engine you support before trusting them.
Five Mistakes To Avoid
The most frequent error is forgetting the container-type declaration. You can write a perfect @container rule, but without container-type: inline-size on the ancestor, it will not fire. The second is trying to style the container itself, the @container rule cannot style the element it is contained by. The third is misusing the units in a calc() without testing on older Safari. The fourth is assuming @supports protects you from every bug; it only protects you from missing features. The fifth is overcomplicating the fallback. If a media query breakpoint is good enough for your component, use it. The container query is the right tool when the component's layout depends on the container's size, not the viewport's, and the container is not the same as the viewport. When in doubt, write the base styles, add the @supports guard, and test in the oldest engine you support.
Measure The Performance Cost
Container queries are not free. The layout containment that makes them work changes how the engine computes sizes, and the query evaluation happens on every size change of the container. The performance cost is measurable but usually small. The real cost is in the fallback. If you write the base styles and the container query styles, you are shipping more CSS than a single media query solution. The CSS parser has to parse both blocks, and the engine has to evaluate the @supports test. The @supports test is cheap, but it is not nothing. The bigger cost is in the layout. When a container query fires, the engine has to recalculate the layout of the container's subtree. If you have many containers or deeply nested containers, this can become a bottleneck. Measure with the performance profiler, not with assumptions. In most cases, the cost is acceptable, but if you are building a page with hundreds of cards, each with its own container, consider whether a single media query on the grid parent would achieve the same effect for less.
Container Query Or Media Query?
The decision is simple: use a container query when the component's layout depends on the size of its container, not the viewport. A card in a sidebar and a card in a main column that both need to reflow at the same width are the canonical use case. Use a media query when the layout depends on the viewport size. A page-level grid that changes from one column to two at a certain screen width is a viewport concern. The container query is not a replacement for the media query; it is a supplement. The failure case is when you use a container query because it is the new feature, and a media query would have been simpler. That is over-engineering. The other failure case is when you use a media query for a component that lives in multiple contexts, and the breakpoint is wrong for some of them. For that, the container query is the right tool.
The Honest Caveat
Container queries are the right tool for a specific job, and the production pattern is mature enough to ship today. But the spec is not finished moving. Style queries reached widely available in 2025, and the container units have version-specific bugs that are not in the Baseline documentation. The fallback is not a polyfill; it is a simpler style. If you need the exact behavior of a container query in an engine that does not support it, you cannot have it with CSS alone. You can use a JavaScript library to measure the container and apply classes, but that is a different pattern with its own costs. Use container queries where the component genuinely needs them. Write the base styles first, gate with @supports, and accept that the fallback is a working layout, not a pixel-perfect replica. Measure the performance, test the units, and move on.