# Container Query Proposal & Explainer ## Authors - Miriam Suzanne ## Participate The CSSWG has resolved to begin moving this proposal into the CSS specification, as part of the [CSS Containment Module level 3](https://drafts.csswg.org/css-contain-3/). (So far, that's an empty document. I'm working on it.) Please leave any feedback on the CSSWG issues for this proposal: - [Fleshing out @container queries with single-axis containment](https://github.com/w3c/csswg-drafts/issues/5796) - [Single-axis containment](https://github.com/w3c/csswg-drafts/issues/1031) - [Request for TAG review](https://github.com/w3ctag/design-reviews/issues/592) More specific issues for discussion: See also the [github project for css-contain-3](https://github.com/w3c/csswg-drafts/projects/18). - [:root/body viewport propagation and containment](https://github.com/w3c/csswg-drafts/issues/5913) -- Resolved: no propagation when containment is applied to body or root - ["container width" and "container height" units](https://github.com/w3c/csswg-drafts/issues/5888) - [`srcset` and `sizes` interaction with container queries](https://github.com/w3c/csswg-drafts/issues/5889) - [CQ vs shadow boundaries](https://github.com/w3c/csswg-drafts/issues/5984) - [What container features can be queried?](https://github.com/w3c/csswg-drafts/issues/5989) - [Should there be a new syntax for establishing queryable containers?](https://github.com/w3c/csswg-drafts/issues/6174) - [What is the migration path for Container Queries?](https://github.com/w3c/csswg-drafts/issues/6175) - [Is there a use-case for querying explicit container selectors?](https://github.com/w3c/csswg-drafts/issues/6176) - [Is it possible to apply containment inside @container?](https://github.com/w3c/csswg-drafts/issues/6177) - [How does @container resolve when no ancestor containers have been defined?](https://github.com/w3c/csswg-drafts/issues/6178) -- Resolved: container queries are not applied Implementations: - [Chromium](https://crbug.com/1145970) -- in Chrome Canary, go to `chrome://flags` & "Enable CSS Container Queries" (This is a draft prototype and may not match the final design. I use syntax in this document that is not yet supported in the prototype.) Polyfills: - [CQFill](https://github.com/jsxtools/cqfill) by **Jonathant Neal** -- still a very early prototype/proof-of-concept, requiring both pre-processor compilation (available for PostCSS) and run-time JS (using ResizeObserver). Demos & Articles: - [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries) by **Rachel Andrew** - [Container Queries: a Quick Start Guide](https://www.oddbird.net/2021/04/05/containerqueries/) by **David Herron** - [A Primer On CSS Container Queries](https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/) by **Stephanie Eckles** - [Next Gen CSS: `@container`](https://css-tricks.com/next-gen-css-container/) by **Una Kravets** - [Container Queries are actually coming](https://piccalil.li/blog/container-queries-are-actually-coming) by **Andy Bell** - [Say Hello To CSS Container Queries](https://ishadeed.com/article/say-hello-to-css-container-queries/) by **Ahmad Shadeed** \ ([Annotated on CSS Tricks](https://css-tricks.com/say-hello-to-css-container-queries/) by **Robin Rendle**) - [CSS Container Queries: A First Look + Demo](https://www.bram.us/2021/03/28/css-container-queries-a-first-look-and-demo/) by **Bramus Van Damme** - [Awesome-Container-Queries](https://github.com/sturobson/Awesome-Container-Queries) by **Stuart Robson** - [My collection of codepen demos](https://codepen.io/collection/XQrgJo) This Document: - [On github](https://github.com/oddbird/css-sandbox/blob/main/src/rwd/query/explainer.md) - [On css.OddBird.net](https://css.oddbird.net/rwd/query/explainer/) - Typos or other [issues can be reported in github](https://github.com/oddbird/css-sandbox/issues) ## Table of contents - [Authors](#authors) - [Participate](#participate) - [Table of contents](#table-of-contents) - [Introduction](#introduction) - [Goals](#goals) - [Non-goals](#non-goals) - [Proposed Solutions](#proposed-solutions) - [Single-axis containment (`inline-size` & related values)](#single-axis-containment-inline-size--related-values) - [Containment context](#containment-context) - [Container queries (`@container`)](#container-queries-container) - [Container features](#container-features) - [Container query list](#container-query-list) - [Key scenarios](#key-scenarios) - [Modular components in any container](#modular-components-in-any-container) - [Components with internal containers](#components-with-internal-containers) - [Component in a responsive grid track](#component-in-a-responsive-grid-track) - [Combining media & container queries](#combining-media--container-queries) - [Detailed design discussion & alternatives](#detailed-design-discussion--alternatives) - [Single-axis containment issues](#single-axis-containment-issues) - [Implicit vs explicit containers](#implicit-vs-explicit-containers) - [Combining scope with container queries](#combining-scope-with-container-queries) - [@-Rule or pseudo-class?](#-rule-or-pseudo-class) - [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition) - [References & acknowledgements](#references--acknowledgements) - [Changelog](#changelog) - [2021.04.02](#20210402) - [2021.03.26](#20210326) - [2021.01.29](#20210129) ## Introduction Media-queries allow an author to make style changes based on the overall viewport dimensions -- but in many cases, authors would prefer styling modular components based on their context and available space within a layout. Earlier this year, David Baron & Brian Kardell proposed two complementary approaches to explore: a [`@container` rule][dbaron-cq], and a [`switch()`][switch] function. Both could be useful in different cases. [dbaron-cq]: https://github.com/dbaron/container-queries-implementability [switch]: https://bkardell.com/blog/AllThemSwitches.html This proposal builds on David Baron's `@container` approach, which works by applying size & layout containment to the queried elements. Any element with both size & layout containment can be queried using a new `@container` rule, with similar syntax to existing media-queries. Currently, size containment is all-or-nothing. In order to make that less restrictive for authors, I'm also proposing `inline-size` & `block-size` values for the `contain` property. This is an early outline of the feature as I imagine it in the abstract -- but there are a number of questions that could only be resolved with a prototype. The purpose of this document is to outline a direction for more testing & exploration. ## Goals Often the layout of a page involves different "container" areas -- such as sidebars and main content areas -- and then component parts that can be placed in any area. Those components can be complex (a full calendar widget) or fairly simple (heading typography), but should respond in some way to the size of the container. This can happen at multiple levels of layout -- even inside nested components. The important distinction is that the "container" is distinct from the "component" being styled. Authors can query one to style the other. ## Non-goals Modern layouts provide a related problem with slightly different constraints. When using grid layout, for example, available grid-track size can change in ways that are difficult to describe based on viewport sizes -- such as shrinking & growing in regular intervals as new columns are added or removed from a repeating `auto-fit`/`auto-fill` grid. In this case there is no external "container" element to query for an accurate sense of available space. There is a [reasonable workaround](#component-in-a-responsive-grid-track), but an ideal solution might look more like Brian Kardell's [`switch()` proposal][switch] -- which allows a limited set of properties to query the space available, rather than any explicit container. It would be good to consider these approaches in tandem. Improvements to flexbox & grid -- such as [indefinite grid spans](https://github.com/w3c/csswg-drafts/issues/388) or [first / last keywords](https://github.com/w3c/csswg-drafts/issues/2402) -- could also improve on the existing flexibility of those tools. And finally, authors often want to _smoothly interpolate values_ as the context changes, rather than toggling them at breakpoints. That would require a way to describe context-based animations, with breakpoint-like keyframes. ## Proposed Solutions ### Single-axis containment (`inline-size` & related values) _This is a proposed change to the [CSS Containment Module](https://drafts.csswg.org/css-contain/), specifically [size containment](https://drafts.csswg.org/css-contain/#containment-size)._ In order for container-queries to work in a performant way, authors will need to define container elements with explicit containment on their layout and queried-dimensions. This can be done with the existing `contain` property, using the `size` and `layout` values: ```css .container { contain: size layout; } ``` While that will work for some use-cases, the majority of web layout is managed through constraints on a _single (often inline) axis_. Intrinsic sizing on the cross (often block) axis is required to allow for changes in content, font size, etc. I'm proposing new single-axis values for `contain`, starting with `inline-size`: ```css .inline-container { contain: inline-size; } ``` This is both the most common use-case, and the most likely to be implementable. If we find that it's also possible to support 1D containment on the block axis, we should also support the `block-size` logical value, and `width`/`height` physical values: ```css .block-container { contain: block-size; } .width-container { contain: width; } .height-container { contain: height; } ``` Elements with single-axis containment should have their intrinsic and final layout on the specified axis determined without any contributions from their children. In most cases, that should be the same as current `contain: size` behavior, only applied to the axis in question. Of these values, it is clear that `block-size` has the fewest use-cases, and more potential implementation issues. While I'm not ready to eliminate it immediately, support for `block-size` would not be required to make container queries useful. Since both `width` and `height` values may refer to the block size in a given instance, they pose the same potential issues. See the discussion of [single-axis containment issues](#single-axis-containment-issues) for more detail. ### Containment context Ideally, container queries could be resolved against the _available space_ for any given element. Since size and layout containment are required, we instead need to define the _containment context_ for each element. I propose that any element with appropriate containment on a given axis generates a new _containment context_ in that axis, which descendants can query against: ```css .two-axis-container { /* establishes a new containment context on both axis */ contain: layout size style; } .inline-container { /* establishes a new containment context on the inline axis */ contain: layout inline-size style; } .block-container { /* establishes a new containment context on the block axis */ contain: layout block-size style; } ``` When size-containment is only available on a single axis, queries on the cross-axis will not resolve against that query context. When no containment context is established, [container queries are not applied](https://github.com/w3c/csswg-drafts/issues/6178). ### Container queries (`@container`) This could be added to a future level of the [CSS Conditional Rules Module](https://drafts.csswg.org/css-conditional-3/). Unless otherwise noted, `@container` should follow the established specifications for _conditional group rules_. The `@container` rule can be used to style elements based on their immediate _containment context_, and uses a similar syntax to existing media queries: ```css /* @container { } */ @container (inline-size > 45em) { .media-object { grid-template: "img content" auto / auto 1fr; } } ``` This would target any `.media-object` whose _containment context_ (nearest ancestor with containment applied) is greater-than `45em`. Unlike media-queries, each element that is targeted by a conditional group rule will need to resolve the query against its own containment context. Multiple elements targeted by the same selector within the same group may still resolve differently based on context. Consider the following CSS & HTML together: ```css /* css */ section { contain: layout inline-size; } div { background: red; } @container (inline-size > 500px) { div { background: yellow; } } @container (inline-size > 1000px) { div { background: green; } } ``` ```html
green background
yellow background (resolves against container 2)
red background
``` #### Container features Like media-queries, `@container` needs a well defined list of "features" that can be queried. The most essential container features are the contained dimensions: - physical dimensions: `width` / `height` - logical dimensions: `inline-size` / `block-size` When containment is available on both axis, we might also be able to query dimensional relationships such as: - `aspect-ratio` - `orientation` Since container queries resolve against styled elements in the DOM, it may also be possible to query other aspects of the container's computed style? - `inline-content-box` - `font-size` - etc. This needs more discussion and fleshing-out. #### Container query list Like media-queries, container-queries can be combined in a list, using the same syntax and following the same logic as media-query-lists: > A media query list is true if any of its component media queries are true, > and false only if all of its component media queries are false." > > --[Media Queries 4](https://www.w3.org/TR/mediaqueries-4/#mq-list) ## Key scenarios ### Modular components in any container Page layouts often provide different layout "areas" that can act as containers for their descendant elements. These can be nested in more complex ways, but let's start with a sidebar and main content: ```html
...
``` We can establish a responsive layout, and declare each of these areas as a _containment context_ for responsive components: ```css body { display: grid; grid-template: "main" auto "aside" auto / 100%; } @media (inline-size > 40em) { body { grid-template: "aside main" auto / 1fr 3fr; } } main, aside { contain: layout inline-size; } ``` Now components can move cleanly between the two areas -- responding to container dimensions without concern for the overall layout. For example, some responsive defaults on typographic elements: ```css h2 { font-size: 120%; } @container (inline-size > 40em) { h2 { font-size: calc(130% + 0.5vw); } } ``` Or "media objects" that respond to available space: ```css .media-object { grid-template: "img" auto "content" auto / 100%; } @container (inline-size > 45em) { .media-object { grid-template: "img content" auto / auto 1fr; } } ``` ### Components with internal containers A more complex component, like a calendar, might reference external context while also defining nested containers: ```html
...
...
...
...
``` ```css .day { contain: layout inline-size; } /* despite having different containers, these could share a query */ @container (inline-size > 40em) { /* queried against external page context */ .calendar { grid-template: repeat(7, 1fr); } .day { border: thin solid silver; padding: 1em; } /* queried against the day */ .event { grid-template: "img content" auto / auto 1fr; } } ``` ### Component in a responsive grid track In some situations, there is no clear "container" element defining the available space. Consider the following HTML & CSS: ```html
...
...
...
...
``` ```css .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(20em, 1fr)); } .card { display: grid; /* we want to change this value based on the track size */ grid-template: "image" auto "content" 1fr "footer" auto / 100%; } ``` The size of `.card-grid` does not accurately reflect the available space for a given card, but there is no other external "container" that `.card` can use to adjust the `grid-template`. Authors using this proposal would need to add an extra wrapping element -- so that the card component has an external track-sized container to query: ```html
...
...
...
...
``` ```css /* the outer element can get containment… */ .card-container { contain: layout inline-size; } /* which gives .card something to query against */ @container (inline-size > 30em) { .card { grid-template: "image content" 1fr "image footer" auto / 1fr 3fr; } } ``` There are already many similar situations in CSS layout -- so this might be a viable solution for most use-cases -- but the extra markup is not ideal. ### Combining media & container queries In many cases, container queries would replace existing media-queries -- making the same underlying goals more clear and reliable. Often media-queries will remain in place for managing page-wide concerns, like responsive font sizing or outer page layouts. Often I expect the layout areas generated by media-queries to become containers for their descendant components. As discussed above: ```css body { display: grid; grid-template: "main" auto "aside" auto / 100%; } @media (inline-size > 40em) { body { grid-template: "aside main" auto / 1fr 3fr; } } main, aside { contain: layout inline-size; } ``` But there are also situations where an author might want to query container width, along with viewport height. This allows a design to respond to both dimensions, without requiring containment on both axis, or a defined container height: ```css @media (height > 35em) { @container (width > 40em) { .card { grid-template: "img header" auto "img main" 1fr "footer footer" auto / minmax(15em, 1fr) 3fr; } } } ``` Beyond basic dimensions, it's likely that container queries will also be combined with queries of media type, interface, or preferences. For example, a container query that is specific to print media: ```css @media print { @container (width > 20em) { .card { /* ... */ } } } ``` ## Detailed design discussion & alternatives ### Single-axis containment issues CSSWG issue: - [do we need size containment in a single dimension to enable container queries?](https://github.com/w3c/csswg-drafts/issues/1031) There are two known situations in CSS where changes on the block-axis can have an impact on the inline-axis layout: 1. When an ancestor of the contained element has `auto` scrolling, extra cross-axis size can cause scrollbars to appear on the contained-axis. This is only an issue when all three are true: - Scrollbars are part of the layout flow (they are not overlaid) - Overflow on the cross-axis is set to `auto` on _any ancestor_ - The contained size is impacted by the size of that ancestor 2. Block-axis percentage padding & margins are resolved relative to the inline available size. That would cause issues when: - Containment is on the block-axis - Any ancestor has inline-size determined by contents (float+auto, min-content, max-content, etc) - Any intermediate ancestor has: - box-sizing of border-box - height determined by the outer ancestor - % padding on the block-axis (so inner-height decreases as outer-width increases) - The container block-size is impacted by the inner-size of that ancestor See [contain-y comment](https://github.com/w3c/csswg-drafts/issues/1031#issuecomment-379463428)) and related [codepen demo](https://codepen.io/anon/pen/aYQLvV?editors=1100). That issue is most likely to occur when containing the block-axis, but nested writing modes can flip the impacted axis ([contain-x with writing modes](https://github.com/w3c/csswg-drafts/issues/1031#issuecomment-722980450)). There are likely more issues that would be revealed during implementation -- but I expect the number to remain low. These are not entirely new issues. The sizing/layout specs all have module-specific caveats for handling percentages based on available size. The proposal is to begin prototyping this feature, and attempt to address each issue as they arise -- using similar workarounds. For example: - For the sake of determining auto scrollbars on ancestors, the container contributes an infinite cross-axis size (always trigger the scrollbar). This is probably the more common edge-case, but in many cases auto-scrollbars imply an element has containable size on the cross-axis -- so authors could avoid this by using 2D size containment in those cases? - For the sake of resolving percentage-padding on the contained axis, always resolve to auto. This seems to be the existing first-pass behavior in many cases where an element has unknown size. Another option would be to drop `block-size` from the proposal. Those are not final solutions, but examples for how we might be able to solve each case as it arises. ### Implicit vs explicit containers CSSWG issue: - [Is there a use-case for querying explicit container selectors?](https://github.com/w3c/csswg-drafts/issues/6176) In conversations leading to this proposal, there has been some concern about the dangers of establishing context _implicitly_ based on the value of `contain`. Similar behavior for positioning & stacking has sometimes been confusing for authors. [David Baron's proposal][dbaron-cq] included a selector for querying a container more explicitly: ```css /* syntax */ @container ()? { /* ... */ } /* example */ @container .media-object (inline-size > 45em) { .media-object { grid-template: "img content" auto / auto 1fr; } } ``` Since all known use-cases attempt to query the _most immediate available space_, I don't see any need for querying containers with an explicit syntax, or any way to "skipping over" one container to query the next. Adding a selector to the query would also raise new problems: - Explicitly targeted queries are less modular, so components would not be able to query _whatever space they happen to be in_. - It adds potential confusion about what selectors are allowed in the block -- since authors would not be able to style the container itself. However, it might be helpful to consider a more explicit way of defining the containers initially, to make this more clear for authors -- such as `query`, `inline-query`, & `block-query` values that would apply both layout and size containment. This needs more discussion & consideration. ### Combining scope with container queries David Baron's proposal also uses the explicit container selector to attach the concept of `scope` to containers -- only matching selectors inside the query against a subtree of the DOM. This might be useful for use-cases where a component both: - Establishes its own containment context, and - Establishes its own selector scope But in my exploration of use-cases, it seems more common that components will want to query _external_ context, while establishing _internal_ scope. There is also a mis-match where authors expect to style the root element of a given scope, but should not be able to style the root of a container-query. For those reasons, I think the two features -- container queries and scope -- should remain distinct, and be solved separately. ### @-Rule or pseudo-class? Many proposals & Javascript implementations use a pseudo-class rather than an @-rule. ```css /* pseudo-class */ .selector:container() { /* ... */ } ``` I think the @-rule block provides several advantages: - The @-rule syntax matches more closely with existing conditional rules, and builds on existing query-list syntax. - It's likely that a responsive component will have multiple moving parts, and each might require unique selectors based on the same query. These can be grouped in an @-rule. The issue of making multiple changes in a single query could also be resolved with a selector syntax once CSS Nesting has been implemented. These would likely have the same meaning: ```css @container (inline-size < 40em) { & .card { /* ... */ } } .card:container(inline-size < 40em) { /* ... */ } ``` There's even a potential advantage here for the selector syntax, as it allows authors to query a container at a different point in the selector (not just the targeted element). ```css .card:container(inline-size < 40em) { & h2 { /* nesting syntax... */ } } ``` That's not easily represented by any `@container` syntax. ## Stakeholder Feedback / Opposition - Chromium : Positive -- Google was involved in developing this proposal - Gecko : Positive -- Mozilla developed the original proposal this is based on - Webkit : No signals ## References & acknowledgements This proposal is based on the previous work of many people: - Brian Kardell: [All Them Switches][switch] - David Baron: [Thoughts on an implementable path forward][dbaron-cq] - Mat Marquis: [A rough proposal for syntax](https://github.com/WICG/container-queries/issues/2) - Matthew Dean: [2019 Proposal/Solution for Container Queries](https://github.com/WICG/container-queries/issues/12) - Viktor Hubert: [Container Query Plugin](https://github.com/ZeeCoder/container-query/blob/master/docs/syntax.md#Queries) - WICG: [Use Cases and Requirements](https://wicg.github.io/cq-usecases/) - And more: [Who is Working on Container Queries](http://whoisworkingoncontainerqueries.com/) Thanks also for valuable feedback and advice from: - Adam Argyle - Amelia Bellamy-Royds - Anders Hartvoll Ruud - Chris Coyier - Christopher Kirk-Nielsen - Eric Portis - Ethan Marcotte - Florian Rivoal - Geoff Graham - Gregory Wild-Smith - Ian Kilpatrick - Jen Simmons - Kenneth Rohde Christiansen - Lea Verou - Martin Auswöger - Martine Dowden - Mike Riethmuller - Morten Stenshorne - Nicole Sullivan - Rune Lillesveen - Scott Jehl - Scott Kellum - Tab Atkins - Theresa O’Connor - Una Kravets ## Changelog ### 2021.05.11 - NOTE: Link to recent articles - CHANGE: [CSSWG resolved not to use root containment as a fallback][6178] [6178]: https://github.com/w3c/csswg-drafts/issues/6178 ### 2021.04.02 - NOTE: Open & link CSSWG issues for further discussion ### 2021.03.26 - NOTE: CSSWG Resolution and link to Containment Level 3 - NOTE: Chrome experimental prototype information - NOTE: Link to demos & articles ### 2021.01.29 - ADD `width` & `height` physical-dimension containment - EXAMPLES of media & container queries used together