-
Notifications
You must be signed in to change notification settings - Fork 682
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[css-conditional] [css-contain] Fleshing out @container queries with single-axis containment #5796
Comments
There has been some useful feedback in various channels, so I've been updating and maintaining a full explainer with a changelog in my own repo. It's mostly minor clarifications at this point, but I wanted to make sure the latest updates are available from this thread as well. |
The CSS Working Group just discussed
The full IRC log of that discussion<fantasai> Topic: @container queries with single-axis containment<ChrisL> git? <astearns> github: https://github.com//issues/5796 <fantasai> miriam: Wanted to take the time to walk through presentation <miriam> https://slides.oddbird.net/csswg/f2f2102/ <fantasai> miriam: slides here <fantasai> miriam: A number of use cases around container queries <fantasai> miriam: a few links ppl can follow for more complex and simpler use cases <fantasai> miriam: with MQ, we can change all instances of an element and will all change based on viewport <fantasai> miriam: breakpoint established, create a new layout for e.g. this card <fantasai> miriam: but even if those cards are in different parts of layout where they have different space available to them, will change based on viewport <fantasai> miriam: don't want to respond based on viewport though, want to respond based on size of container <fantasai> miriam: 2 proposals brought up last year <fantasai> miriam: dbaron's @container proposal <fantasai> miriam: and bkardell's switch() <fantasai> miriam: interesting tradeoffs, handle different cases <fantasai> miriam: not necessarily competing <fantasai> miriam: but main idea, I'm following up on dbaron's proposal <fantasai> miriam: it covers a lot of the use cases but requires establishing containers and being able to establish containers <fantasai> miriam: and have cards inside containers that can respond to containers they're in <fantasai> miriam: container can have nested containers <fantasai> miriam: each card is rsponding to its own container, even as size of window changes <fantasai> miriam: so to make that work, have to describe container itself <fantasai> miriam: to establish containers in this proposal <fantasai> miriam: what's required is having size and layout containment <fantasai> miriam: byt applying size and layout containment, we can establish them as containment contexts <fantasai> miriam: a bit of a problem here also noted by dbaron <fantasai> miriam: size containment is very invasive <fantasai> miriam: if always have to explicitly set height and width, doesn't work for many use case <fantasai> miriam: need 1D containment <fantasai> miriam: developing prototype to prove that can work <fantasai> miriam: for inline axis <fantasai> miriam: for block axis, a bit more complicated <bkardell_> q+ if there are use cases for block axis even? <fantasai> miriam: if that doesn't work, then width/height can't be made to work because can be iether inline or block <fantasai> miriam: most essential however is inline-size <fantasai> miriam: dbaron's syntax looks like this <astearns> zakim, open queue <Zakim> ok, astearns, the speaker queue is open <fantasai> @container <selector> (<query>) { <rules> } <fantasai> miriam: If we have to be explicity about which containers we're looking at, less flexible <bkardell_> q+ if there are use cases for block axis even? <fantasai> miriam: really what we want is to say h2 or card can respond to whatever container it's in, and wherever we put it can respond <bkardell_> q+ i <fantasai> miriam: modular that way <fantasai> miriam: proposal is to remove the selector block <fantasai> miriam: just describe the query, not the container that it will match against <fantasai> miriam: and instead we use the DOM tree to find the container that we're in <bkardell_> q- i <bkardell_> q+ <fantasai> miriam: so each card looks up its ancestors to the first container that has the limits we're querying <fantasai> miriam: code example [slide] <fantasai> @container (min-width: 40ch) { ... change grid template ... } <fantasai> miriam: that would work in any container <fantasai> miriam: h2 is good example of why this needs to be very modular <fantasai> miriam: might want to use h2 in any container, not defining specifically how one component work <fantasai> miriam: tree can be nested however <fantasai> miriam: if both are h2, they each look up their nearest ancestor container and respond to size of that container <fantasai> [shows tree] <fantasai> miriam: rules in @container apply when aancestor with appropriate containment, and query applies <fantasai> miriam: query against actual values on element, not the viewport, can look at e.g. actual size of the element <fantasai> s/size/font size/ <fantasai> miriam: Could we have queries that respond to font size on container / element? <fantasai> miriam: containment is external <fantasai> miriam: so can't query e.g. size available to you in a grid track, always querying the element itself <fantasai> miriam: that's one place where the switch proposal would be a bit more powerful <fantasai> miriam: but this is also something we can work around by adding an intermediate layer <fantasai> miriam: add an element that's a container as the grid item, put the card inside <fantasai> miriam: some talk of alternate syntax based on pseudo-class <fantasai> miriam: :container(query) <fantasai> miriam: Some downsides and some positives <fantasai> miriam: adding in nesting, becomes quite similar <fantasai> miriam: this provides a few use cases that the @container wouldn't <fantasai> miriam: a lot of other open questions <fantasai> miriam: some of them already have issues, some don't <fantasai> miriam: we can dig into any of that later <fantasai> miriam: links to explainer, issue, TAG review <fremy> q+ <Rossen_> ack bkardell_ <astearns> https://github.com/oddbird/css-sandbox/blob/main/src/rwd/query/explainer.md <fantasai> bkardell_: I think this is great <ChrisL> q+ <fantasai> bkardell_: question when presenting, are there known use cases for block direction? <fantasai> miriam: think would be similar to viewport height use cases <fantasai> miriam: those are rare <fantasai> bkardell_: I found use cases for thing you're doing first, easier part with very strict containment <fantasai> bkardell_: plenty for inline-axis <fantasai> bkardell_: much more than I thought there were initially <fantasai> bkardell_: I actually agree with you that nesting is super-important to work out for a whole bunch of things <astearns> tag review: https://github.com/w3ctag/design-reviews/issues/592 <fantasai> bkardell_: we're making a lot of choices differently; nesting is big deal, one of the biggest things we could work on <fantasai> bkardell_: would like us to prioritize <fantasai> bkardell_: also want to express that this is awesome, and would love to coordinate some more between Igalia and Google who are doing this work <fantasai> bkardell_: to make alignments <Rossen_> q? <fantasai> bkardell_: some similarities in internals that would be good to sort out <fantasai> bkardell_: some opportunities to sugar things as well <Rossen_> ack fremy <fantasai> fremy: very good proposal, like the feel of it <fantasai> fremy: I am wondering if makes sense to say that everything containing something becomes a container <fantasai> fremy: sometimes you need that for perf reasons, be weird if that also changes layout in CSS <fantasai> fremy: maybe add a new value to get the behavior <fantasai> fremy: Something that you can do with this that can't otherwise <fantasai> fremy: can use these containers to set display:none to things <fantasai> fremy: which would change counters etc. <fantasai> fremy: maybe we need a bit more containment that just size? <fantasai> fremy: Unsure also about how easy to implement <fantasai> miriam: Anders and Rune have been working on that, idk if they're here <fantasai> fremy: globally I like the idea, if I make one change to proposal, if we don't use selectors about what is a container, then need to have an explicit way to indicate which is a container <fantasai> miriam: solution you mentioned, of explicit value for contain, that would address the issues you raised <fantasai> chrishtr: was this about counters? <fantasai> miriam: potential of accidentally creating a container somewhere <fantasai> miriam: I haven't found use cases where you want to skip a containment box <fantasai> miriam: but being more explicit can be handled by a specific value <fantasai> miriam: also way it relates to counters, might need style containment <fantasai> miriam: bulky if contain: inline-size layout style <fantasai> miriam: maybe can be combined into that explicit value <fantasai> fremy: exactly what I had in mind <Rossen_> ack ChrisL <fantasai> leaverou2: I wanted to say, 2 syntaxes proposed at-rule vs pseudo-class <fantasai> leaverou2: is pseudo-class actually doable? <fantasai> leaverou2: they match at an earlier stage, can't match at computed value time <fantasai> TabAtkins: generally speaking yes <argyle> q+ <fantasai> TabAtkins: theoretically don't need to if there's not a computation loop <fantasai> TabAtkins: could do another pass later, but usually want to avoid <fantasai> leaverou2: thought significant pushback against 2 passes <fantasai> fremy: but in this case we need it anyway <fantasai> leaverou2: if both fair game, I think pseudo-class syntax allows combining with other selector criteria if both doable <fremy> s/fremy/florian/ <Rossen_> ack argyle <fantasai> argyle: so many fun use cases, breakdown so good <fantasai> argyle: one of the difference between switch and container syntax <fantasai> argyle: question of what am I targetting <fantasai> argyle: switch can know its own size <fantasai> argyle: that's an important use case <fantasai> argyle: container might have 20 items in it, what's the space I'm given? <fantasai> argyle: not sure how to wrestle with those thoughts <fantasai> argyle: pseudo-class selector might be opportunity to specify which container <fantasai> argyle: what if want immediate parent? <fremy> btw, this won't require just two pass, it would require one pass per element, but each pass might require a layout pass in between <una> +1 to the individual container <fremy> I should just say that on call, nevermind <fremy> q+ <fantasai> miriam: wouldn't be immediate parent, would be nearest contained parent <fantasai> argyle: what if you want container a few steps up? <fantasai> miriam: Question has come up, but haven't come up with any use cases for it <fantasai> miriam: wrt switch, agree, that's why these are parellel solutions <fantasai> miriam: with switch(), element queries itself, but is limited in how much can be changed <fantasai> miriam: can't change width based on width <fantasai> miriam: limited properties, but querying actual self space <fantasai> miriam: but with this one querying something external, but can change anything inside it <fantasai> miriam: address differnet use cases with a bit of overlap, both worth pursuing <Rossen_> q? <Rossen_> ack fremy <fantasai> fremy: Wrt lea's multiple passes <fantasai> fremy: You don't need to have multiple passes <fantasai> fremy: need one pass for element <fantasai> fremy: but need to do layout <fantasai> fremy: [missed] <fantasai> fremy: One case I feel a big issue <fantasai> fremy: proposal of containing only in one dimension <una> q+ <fantasai> fremy: that's the only case you have to do layout and then styling inside container <fantasai> fremy: then have to do layout of everything again <fantasai> fremy: that could change size of container <fantasai> fremy: that one is much more tricky, and could do layout many times <fantasai> fremy: multiplied by nesting of containers <florian> s/[missed]/you only need to layout outside the container in one pass, and inside in the second pass, so it's not really two passes/ <astearns> s/[missed] you just have to stop and start at container boundaries (instead of two pass) <fantasai> fremy: bigger problem, not easy to solve <Rossen_> q? <fantasai> fremy: if you have only one, unsure if there's any restriction that we don't have to do the layout of everything in a nested way, so maybe file an issue that and think about it <fantasai> miriam: issue opened already <fantasai> Rossen_: OK, good presentation, what do you want to ask WG? <fantasai> miriam: support for going forward, and feedback like I'm getting <fremy> s/[missed]/between the styling passes <fantasai> una: For slide 15, you have great diagram of initial container and then blue and pink items wider and vertical items <fantasai> una: theoretically same component <fantasai> una: What I see is that you set containment once, on parent <fantasai> una: each individual element querying? <fantasai> miriam: matched with this code [other slide] <Rossen_> ack una <fantasai> miriam: containment on sidebar, main, grid-items <fantasai> miriam: those 5 containers <fantasai> miriam: single card component appears in each container <fantasai> miriam: each one querying its most immediate container <fantasai> una: so containment on immediate parent ,and cna nest <fantasai> una: ancestor. You pick the closest ancestor with container <fantasai> una: in theory, could you set containment on body? <fantasai> una: and have on any child <fantasai> miriam: it would act similar to a media query <emilio> q+ <fantasai> miriam: main advantage would be responding to actual font size not environment font size <fantasai> miriam: if ancestor ... <bkardell_> "root container" <fantasai> miriam: no container, maybe fall back too root to be smarter <fantasai> una: is it necessary to set containment on children? <fantasai> miriam: in order to query something's width, we have to set containment on it <fantasai> miriam: in order to avoid the loops <fantasai> miriam: if only set on body, we can't query the width of the grid item <Rossen_> ack emilio <fantasai> emilio: There were some versions of proposal that support selectors <fantasai> emilio: what happens with Shadow DOM there is a bit weird <fantasai> emilio: stuff like ancestors might not even be in rendering tree at all <fantasai> emilio: but might be DOM ancestor, ancestor of shadow host <fantasai> emilio: descendant combinators wouldn't work <fantasai> emilio: that probably involves more complexity <fantasai> emilio: we can probably come up with some reasonable thing to do, but... <Rossen_> q? <fantasai> Rossen_: hear a lot of support and excitement about this <fantasai> Rossen_: anyone opposed to adopting it? <fantasai> chrishtr: Would be new module, not addition to existing? <leaverou> +1 from Chris and Lea <argyle> q+ <fantasai> miriam: potential for it to happen in Containment and Conditional, or could be its own <fantasai> argyle: pushback I've heard is that 1D doesn't sound as useful as 2D? <fantasai> argyle: if I can't know the width and height of something together, how can I make a good decision? <fantasai> miriam: you can, but would have to contain both <fantasai> miriam: you have to contain whichever dimension you query <Rossen_> ack argyle <fantasai> miriam: but containing both dimensions doesn't address a lot of use cases <fantasai> miriam: block-only containment is hard, we're unsure if we can do it <fantasai> Rossen_: ... <fantasai> florian: If we want to pursue this, need to pursue 1D containment on its own <fantasai> florian: that goes in css-contain <fantasai> florian: but needs further work <fantasai> florian: rest of it [missed] <fantasai> miriam: that's being worked on at Chrome <fantasai> Rossen_: Spec-wise, where are we in 1D containment <fantasai> florian: discussion in GH that it's likely to be possible, but that's as far as we got <Rossen_> q? <fantasai> bkardell_: 1 issue with some back and forth, that's all <fantasai> bkardell_: I agree would make sense that it goes there, and is prerequisite for the rest <fantasai> bkardell_: but no reason not to figure out where the rest of it goes <fantasai> bkardell_: rest of it, if we put it in a spec, would be dependent on 1D containment <TabAtkins> fantasai: My suggestion would be to put the whole thing as Contain 3, and we can figure out exactly where bits go later as we work on it. <TabAtkins> fantasai: Either way, 1d-containment needs to be in Contain <Rossen_> ack fantasai <TabAtkins> fantasai: Draft as a diff spec for now, you can pull things back if you need, otherwise l3 will be all CQs <bkardell_> is that where we would want to put switch too? <nicole> It's so exciting that this is happening. :) <fantasai> florian: Unsure if contain-2 is that close to CR, but also not opposed to what fantasai said <una> +1 to the excitement :D <bkardell_> +1 excitement <jensimmons> +1 <argyle> 🎉 <leaverou> +1 excitement! <fantasai> Rossen_: Proposal here is to add this to css-contain-3 <fantasai> Rossen_: Only question I have is, who works on this besides Miriam? <tantek> +1 progress! <fantasai> florian: I'm already editing level 1 and 2, so happy to tag along with 3 <fantasai> florian: will focus on containment rather than querying, but <fantasai> Rossen_: so level 2 editors + Miriam <bkardell_> q+ <una> WOOO! <chrishtr> Excellent!!!!! <fantasai> RESOLVED: Define container queries in css-contain-3, editors L2 editors + Miriam <nicole> 🎉 <bkardell_> q- I guess if it is going to not happen, but my question above seems relevant |
@mirisuzanne THANK YOU so much for this, and thank you for acknowledging my small contribution! I'm really excited about this and hope it happens! Love the at-rule syntax! ❤️ |
TL;DR -- ignore this post, it appears I misunderstood some of the fundamentals of this proposal, which @mirisuzanne later clarified.
e.g. @container * (width > 45em) {
.media-object {
grid-template: "img content" auto / auto 1fr;
}
}
Okay, that's enough of my $0.02. Thanks so much for moving this along! |
MAYBE I'm mistaken, but if you do want to scope it, don't you do something like: @contain (width > 45em) {
.theScopeIWant .elementSelected {
...
}
} |
I love this. Gonna make so much stuff easy and beautiful. I think that keeping a selector out of it makes sense, out keeps the syntax similar to ❤️ |
@cssinate I don't believe so. The container scope should be the immediate parent that you're querying. Everything inside is within that queried container, which is why I believe this syntax is potentially confusing. e.g. to limit it to a particular scope, if I'm reading this spec correctly, you would have to be able to do: @contain .theScopeIWant (width > 45em) {
.elementSelected {
...
}
} That would apply the query to any (However, @mirisuzanne may be the best one to clarify. I'm honestly not 100% sure either.) |
My slides are archived here as a PDF, for anyone interested: https://lists.w3.org/Archives/Public/www-archive/2021Feb/att-0002/Container_Queries_Proposal_-_vF2F_2021-02-09.pdf
@matthew-dean Not exactly. @cssinate was right about this. The entire selector is not scoped to a container - only the target element. My goal is for containment context to match positioning context. So you can do: .theScopeIWant { contain: <something>; }
@container (width > 45em) {
.theScopeIWant .elementSelected { ... }
} I'm happy to discuss that more, and provide more of my reasoning. In the end it might even be possible to support both. But I think at this point it would be best handled in a dedicated issue. Do you want to open that issue, and link back to this conversation? (if not, I'll get to it soon) |
Nice work❤️❤️.I'm really excited about this |
@mirisuzanne Thanks for the clarification. I think I may be misunderstanding what @cssinate means by scope, or a fundamental piece of this proposal. I guess maybe what you're saying is that you can limit the scope of a query by placing the scope within the realm of the So, I think what you're saying is that you don't need a selector because you can already say, via the child selector within @container (width > 45em) {
.theScopeIWant .elementSelected {
...
}
} One of the things I tried to illustrate with my proposal is that everyone thinks about this problem very differently! If I misunderstood some of the fundamentals of your proposal, I apologize! I honestly feel this is a far cleaner syntax than what I proposed, but it seems my fear that someone wouldn't get the mental model right should have been self-applied lol. So to clarify, this: @container (width > 45em) {
.card { ... }
h2 { ... }
} is essentially similar to: .card:container(width > 45em) { ... }
h2:container(width > 45em) { ... } In that, it applies to I think this is such a departure from previous proposals that maybe I didn't quite get it. That being the case, I'm not sure another issue needs to be raised because I'm not sure there is one? If I misunderstood (and am understanding it better now?) then I'm happy to remove my previous post so that I don't confuse anyone else. Thanks for taking the time to respond! |
@matthew-dean yep, you got it. Exactly. No need to remove anything - these are exactly the conversations we need to be having, and help clarify the language. I'm glad for your input. (Issues are free, and don't have to represent a problem – just a decision that needs to be made, or a question that needs more clarity. I'll soon start opening a number of them against my own proposal, as I start writing the spec. Feel free to do the same if you (anyone reading this) have other questions, or followup. Issues can be labeled with |
This is fantastic news! Congrats everyone, and thanks CSSWG! 😍 |
A thought about scoped container queries (I hope I didn't miss something written about this before) The first thing I thought about when reading the scoped container section was the I thought of something like - @container someNamedQuery (width > 45em) {
...
}
@container otherNamedQuery (width > 80em) {
...
}
/* All sections can have one rule (don't mind my naming, I'm bad at it) */
section {
contain: layout size;
container-query: someNamedQuery;
}
/* But we override this rule for sections that should behave differently */
section.special-rules {
contain: layout size;
container-query: otherNamedQuery;
} Or even with a shorthand: @container someNamedQuery (width > 45em) {
...
}
section {
contain: layout size / someNamedQuery;
} |
I know you probably discussed this situation, but anyway
|
Hello, im probably late for the party, but i maybe would like something like this: .sidebar {
width: 100%;
layout: mobile; /* use whatever name you want, these are examples; passed to every child element */
}
@media (min-width:900px){
.sidebar {
width: 200px;
layout: desktop;
}
}
@media (min-width:1600px){
.sidebar {
width: 400px;
layout: desktop-wide
}
}
.sidebar > ul:layout(mobile){
/* styles for mobile */
}
.sidebar > ul:layout(desktop){
/* styles for desktop */
}
.sidebar > ul:layout(desktop-wide){
/* styles for wide desktop */
} the "layout" variable would be available to every child element, so it would be easy to access in case a lot of nested code. Example from less: .sidebar {
...
ul {
...
li {
a {
span:layout(mobile){
display: none;
}
span:layout(desktop-wide){
display: block;
}
}
}
}
} this way containing elements would use name instead of dynamic values, which could be changed at one spot (container settings, sidebar in this case). So in case i would decided i want "desktop" view starting from 1000px not 900px i would change it only once. I know this could be probably achieved by using variables via pre-processors (or maybe native?), but this looks a bit cleaner to me :) |
@wUFr what you suggest looks a lot like viewport wide media queries instead of container queries. If you replace And I think you can mix it with custom properties to pass context to child elements, and use it with this nice trick from Lea Verou: https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/ |
@nhoizey thinking about it, you're right, but it would be nice to have something like this too 🍺 |
@wUFr I agree, you could open another issue for this. |
Thanks @mirisuzanne for continuing to move this forward! |
I would absolutely love to see native container queries realized. One suggestion though: Please think about supporting css-vars inside the queries Example: @container var(--container-medium, (width > 480px)) {
content: "i am displayed inside medium sized containers";
} This would solve many issues mentioned above and add further flexibility. You could provide a component/framework/whatever with good defaults but also support custom values in a very easy way and the base technology for this (css-vars) is already available! integration example (lets assume loading a web-component, that uses container queries): <script type="module" src="//some.cdn/some-component.js"></script>
<style type="text/css">
:root {
--container-medium: (width > 520px);
}
</style> |
@MajPay That wouldn't work in practice. A |
While this exact solution isn't possible, there has been some discussion of adding a new syntax for custom reusable media-queries. If something like that happens, I expect we would now want it to support container queries as well. But that's a separate feature/conversation. |
Well, i understand my technical understanding may be to little to argue on this, but if we end up with something like this: .element {
display: flex;
flex-direction: column;
@container var(--container-medium, (width > 480px)) {
flex-direction: row;
}
} i dont see any problem with that. But if its this is mixin up different topics i see the point in not discussing it further. Anyway - thanks for the reply. |
Add support for 'inline-size' and 'block-size' as keywords for the CSS contain property. There is no specification yet, but there are two github issues[1][2]. The syntax implemented here does not allow more than one size containment value per declaration. [1] w3c/csswg-drafts#1031 [2] w3c/csswg-drafts#5796 Bug: 1146092 Change-Id: I79cf3d4cb22e8e2705d5f2f7348f4513e4eac2d7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2653205 Reviewed-by: Morten Stenshorne <[email protected]> Reviewed-by: Anders Hartvoll Ruud <[email protected]> Commit-Queue: Rune Lillesveen <[email protected]> Cr-Commit-Position: refs/heads/master@{#848577} GitOrigin-RevId: 0ecdc725f612e6899bb452b74650101625bb9815
Update: As feedback comes in from various channels, I've been updating & maintaining a full explainer with a changelog.
Intro
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 within a layout. Earlier this year, David Baron & Brian Kardell proposed two complementary approaches to explore: the
@container
rule, and aswitch()
function. Both seem useful in different situations.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, there are proposed single-axis (inline-size
&block-size
) values for thecontain
property.This is a rough outline of the feature as I imagine it -- but there are a number of questions that could more easily resolved with a prototype. The purpose of this document is to flesh out some direction for more applied testing & exploration.
Table of contents:
Sadly you can't link sections in an issue…
inline-size
&block-size
values)@container
)Goals
Often the layout of a page involves different "container" areas -- such as sidebars and main content areas -- with modular
"component" parts that can be placed in any area. Those components can be complex (a full calendar widget) or fairly simple (basic 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 in this proposal, but a more ideal solution might look more like Brian Kardell's
switch()
proposal. It would be good to consider these approaches together, as they make complementary tradeoffs.There are also proposed improvements to flexbox & grid -- such as indefinite grid spans or first / last keywords -- which would help expand on the responsive nature 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. Scott Kellum has been doing a lot of work in that area.
All of those would contribute towards a larger goal of "responsive components" in CSS. I don't think we can solve it all in a single feature.
Proposed Solutions
Single-axis containment (
inline-size
&block-size
values)This is a proposed change to the CSS Containment Module, specifically size containment -- and already has an issue thread for discussion.
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 thesize
andlayout
values: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. So this proposal would rely on one or two new single-axis values for
contain
, as discussed in #1031:Of these two values, it is clear that
block-size
has the fewer use-cases, and more potential implementation issues. Support forblock-size
would be great, but is not required to make container queries useful.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'm proposing that any element with layout and size containment on a given axis generates a new containment context in that axis, which descendants can query against:
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, there should be a fallback akin to the Initial Containing Block which queries can resolve against.
Container queries (
@container
)This could be added to a future level of the CSS Conditional Rules Module.
The
@container
rule can be used to style elements based on their immediate containment context, and uses a similar syntax to existing media queries. Where possible@container
syntax should follow the established specifications for conditional group rules, and allow queries to be combined in a list:This would target any
.media-object
whose containment context (nearest ancestor with containment applied) is greater-than45em
. When no containment context is established, the Initial Containing Block can be used to resolve the query.Unlike media-queries, each element that is targeted by a conditional group rule will need to resolve the query against its own containment context. Different elements targeted by the same selector within the same query may still resolve differently based on context. Consider the following CSS & HTML together:
Each
div
resolves differently based on its own immediate context.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:width
/height
inline-size
/block-size
When containment is applied 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
This needs more discussion and fleshing-out.
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:
We can establish a responsive layout, and declare each of these areas as a containment context for responsive components:
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:
Or "media objects" that respond to available space:
Components with internal containers
A more complex component, like a calendar, might reference external context while also defining nested containers:
Component in a responsive grid track
In some situations, there is no clear "container" element defining the available space. Consider the following HTML & CSS:
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 thegrid-template
. Authors using this feature would need to add an extra wrapping element -- so that the card component has an external track-sized container to query: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.
Detailed design discussion & alternatives
Single-axis containment issues
The existing issue thread has a proposal for moving forward with single-axis containment despite the known issues.
Implicit vs explicit containers
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 included a selector for querying a container more explicitly:
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:
switch()
proposal.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:But in my exploration of use-cases, it seems 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 addressed separately.
@-Rule or pseudo-class?
Many proposals & Javascript implementations use a pseudo-class rather than an @-rule.
I think the @-rule block provides several advantages:
I also think the syntax can lead to confusion. It's not immediately clear what these different selectors would mean:
Questions to explore
@container
to resolve queries against contained host element?contain
values?References & acknowledgements
This proposal is based on the previous work of many people:
Thanks also for valuable feedback and advice from:
(sorry if I missed anyone… I don't mean to imply all these people has signed off on my proposal, or even seen the whole thing - but they have all provided useful feedback along the way)
The text was updated successfully, but these errors were encountered: