Skip to content
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

Closed
mirisuzanne opened this issue Dec 15, 2020 · 23 comments

Comments

@mirisuzanne
Copy link
Contributor

mirisuzanne commented Dec 15, 2020

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 a switch() 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 the contain 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…

  • Goals
  • Non-goals
  • Proposed Solutions
    • Single-axis containment (inline-size & block-size values)
    • Containment context
    • Container queries (@container)
  • Key scenarios
    • Modular components in any container
    • Components with internal containers
    • Component in a responsive grid track
  • Detailed design discussion & alternatives
    • Single-axis containment issues
    • Implicit vs explicit containers
    • Combining scope with container queries?
    • @-Rule or pseudo-class?
    • Questions to explore
  • References & acknowledgements

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 the size and layout values:

.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. So this proposal would rely on one or two new single-axis values for contain, as discussed in #1031:

.inline-container {
  contain: inline-size;
}

.block-container {
  contain: block-size;
}

Of these two values, it is clear that block-size has the fewer use-cases, and more potential implementation issues. Support for block-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:

.two-axis-container {
  /* establishes a new containment context on both axis */
  contain: layout size;
}

.inline-container {
  /* establishes a new containment context on the inline axis */
  contain: layout inline-size;
}

.block-container {
  /* establishes a new containment context on the block axis */
  contain: layout block-size;
}

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:

/* @container <container-query-list> { <stylesheet> } */
@container (width > 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. 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:

/* css */
section {
  contain: layout inline-size;
}

div {
  background: red;
}

@container (width > 500px) {
  div {
    background: yellow;
  }
}

@container (width > 1000px) {
  div {
    background: green;
  }
}
<!-- html -->
<section style="width: 1500px">
  <div>green background</div>

  <section style="width: 50%">
    <div>yellow background (resolves against inner section)</div>
  </section>
</section>
<section style="width: 400px">
  <div>red background</div>
</section>

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:

  • physical dimensions: width / height
  • logical dimensions: 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
  • etc.

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:

<body>
  <main>...</main>
  <aside>...</aside>
</body>

We can establish a responsive layout, and declare each of these areas as a containment context for responsive components:

body {
  display: grid;
  grid-template: "main" auto "aside" auto / 100%;
}

@media (width > 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:

h2 {
  font-size: 120%;
}

@container (width > 40em) {
  h2 {
    font-size: calc(130% + 0.5vw);
  }
}

Or "media objects" that respond to available space:

.media-object {
  grid-template: "img" auto "content" auto / 100%;
}

@container (width > 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:

<section class="calendar">
  <div class="day">
    <article class="event">...</article>
    <article class="event">...</article>
  </div>
  <div class="day">...</div>
  <div class="day">...</div>
</section>
.day {
  contain: layout inline-size;
}

/* despite having different containers, these could share a query */
@container (width > 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:

<section class="card-grid">
  <div class="card">...</div>
  <div class="card">...</div>
  <div class="card">...</div>
  <div class="card">...</div>
</section>
.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 feature would need to add an extra wrapping element -- so that the card component has an external track-sized container to query:

<section class="card-grid">
  <div class="card-container"><div class="card">...</div></div>
  <div class="card-container"><div class="card">...</div></div>
  <div class="card-container"><div class="card">...</div></div>
  <div class="card-container"><div class="card">...</div></div>
</section>
/* the outer element can get containment… */
.card-container {
  contain: layout inline-size;
}

/* which gives .card something to query against */
@container (width > 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.

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:

/* syntax */
@container <selector> (<container-media-query>)? {
  /* ... */
}

/* example */
@container .media-object (width > 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 contasiner they happen to be in.
  • It adds potential confusion about what selectors are allowed in the block. Authors would not be able to style the container itself, unless we limited the properties allowed -- similar to the 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:

  • Establishes its own containment context, and
  • Establishes its own selector scope

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.

/* pseudo-class */
.selector:container(<query >) {
  /* ... */
}

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.
  • We avoid the issues mentioned above with having an explicit selector attached to the query.

I also think the syntax can lead to confusion. It's not immediately clear what these different selectors would mean:

:container(width < 40em) {
  font-size: small;
}
.media-object:container(width < 40em) {
  font-size: small;
}
:container(width < 40em) .media-object {
  font-size: small;
}

Questions to explore

  • Is it possible to apply containment within a container query? In what cases might that lead to cyclic dependencies?
  • Will a default root fallback container feel like expected behavior, or should we require explicit containment in order to match queries?
  • Are there special considerations required for a shadow-DOM @container to resolve queries against contained host element?
  • Do we need a more explicit value for establishing query context, apart from existing contain values?
  • What features can we support besides dimension-queries?

References & acknowledgements

This proposal is based on the previous work of many people:

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
  • Martin Auswöger
  • Martine Dowden
  • Mike Riethmuller
  • Morten Stenshorne
  • Nicole Sullivan
  • Rune Lillesveen
  • Scott Jehl
  • Scott Kellum
  • Tab Atkins
  • Theresa O’Connor
  • Una Kravets

(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)

@mirisuzanne
Copy link
Contributor Author

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.

@astearns astearns added this to the VF2F-2021-02-11 APAC milestone Feb 2, 2021
@astearns astearns added this to Feb 11 in Feb 2021 vf2f Feb 2, 2021
@astearns astearns moved this from Feb 11 earlier to Feb 11 later in Feb 2021 vf2f Feb 2, 2021
@mirisuzanne mirisuzanne moved this from Feb 11 later to Feb 11 earlier in Feb 2021 vf2f Feb 9, 2021
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed @container queries with single-axis containment, and agreed to the following:

  • RESOLVED: Define container queries in css-contain-3, editors L2 editors + Miriam
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

@matthew-dean
Copy link

@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! ❤️

@matthew-dean
Copy link

matthew-dean commented Feb 12, 2021

TL;DR -- ignore this post, it appears I misunderstood some of the fundamentals of this proposal, which @mirisuzanne later clarified.


I've been thinking about this proposal a lot since I came across it, and I just want to mention something.

When you're querying the viewport with @media, there's essentially a single "object" being queries. While I totally get the argument that with this @container syntax, a selector is not needed, I believe consideration should be given to the fact that omitting a selector, even a star selector, (IMO) makes this conceptually harder to understand for new CSS authors. "What am I querying?" "Well it's the available container dimensions anywhere." " 🤔 "

If you're intimately familiar with CSS and browser layout mechanisms, it's not hard to grok a container query without a selector. But for new authors, I feel like the ability to query a selector should at least be optional, if not recommended.

e.g.

@container * (width > 45em) {
  .media-object {
    grid-template: "img content" auto / auto 1fr;
  }
}

To me, this is an easier mental model, and there's good reason it's been included in almost every container query proposal. It ties into the mental model for CSS authors the "what" of what I'm querying, followed by the query, and IMO is the most analogous to a @media query, in which the "what" is clearly established, not explicitly in the syntax, but implicitly by the nature of the query.

I wouldn't consider this a feature blocker, of course, because I'm so jazzed that this proposal has legs, and honored to have been mentioned, and for me personally, I won't struggle with the mental model. But the more I think about it, the more I worry that the omission of a selector as even an option makes this hard for new devs, because the "what" takes a lot of concept knowledge. Couple that with needing to understand the contain property, and it's a lot to grok.


As to these points arguing against the inclusion of a selector from Dave Baron's proposal:

Explicitly targeted queries are less modular, so components would not be able to query whatever contasiner they happen to be in.

That may be exactly what the author desires! An author may want to write queries which only apply in specific contexts (and change in others.) And to make the container query universal, one would target a * (universal) selector, which is easy for new authors to understand, because it ties into the basics of CSS.

It adds potential confusion about what selectors are allowed in the block.

To me, the inverse is true. A selector, even a star selector, makes the context of the query clear. Omitting a selector in the query muddies the "what", leading to more possible developer confusion. For advanced authors, the option to omit a selector makes a certain sense; you (usually) want this to apply regardless of context. But even for advanced authors, I can see myself wanting to apply a query only within certain contexts, and it would make the feature much more powerful as a result.

There are a lot of very talented people / CSS experts who've weighed into this proposal. My suggestion is just to consider the non-expert. Will they understand the context? What syntax is most intuitive? Have they been asked? I can imagine a person teaching this CSS concept to someone new and saying something like, "Imagine there's a star selector in this query, even though it's not present." That lesson would be made easier if the selector was actually present. (Incidentally, it would also make clear that you can you cannot query the space granted by, say, a display: grid parent which, had I not read the proposal, I would have wrongly intuited was possible just based on the syntax. A container query queries elements. Therefore: which ones?)

Okay, that's enough of my $0.02. Thanks so much for moving this along!

@cssinate
Copy link

@matthew-dean

MAYBE I'm mistaken, but if you do want to scope it, don't you do something like:

@contain (width > 45em) {
  .theScopeIWant .elementSelected {
    ...
  }
}

@maxnordlund
Copy link

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 @media. Having contain create a container context feels like position: relative / position: absolute, or z-indexing. I also like how it sidesteps styling the container itself, which could lead to infinite loops.

❤️

@matthew-dean
Copy link

matthew-dean commented Feb 12, 2021

@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 .elementSelected elements that are immediate children of a .theScopeIWant element, in which .theScopeIWant has an allocated width of at least 45em when calculated.

(However, @mirisuzanne may be the best one to clarify. I'm honestly not 100% sure either.)

@mirisuzanne
Copy link
Contributor Author

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

Everything inside is within that queried container

@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)

@Mr-Array22
Copy link

Mr-Array22 commented Feb 12, 2021

Nice work❤️❤️.I'm really excited about this

@matthew-dean
Copy link

matthew-dean commented Feb 12, 2021

@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 @container query.

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, to only apply the query based on, say, the fact that .elementSelected is a descendent of .theScopeIWant? 🤔 i.e. given this CSS:

@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.card and h2 given that they would be rendering in a space (or container?) that is > 45em?

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!

@mirisuzanne
Copy link
Contributor Author

@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 [css-contain-3] for clarity.)

@tomhodgins
Copy link

This is fantastic news! Congrats everyone, and thanks CSSWG! 😍

@tombigel
Copy link

tombigel commented Feb 15, 2021

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 @keyframes syntax - why not name queries so they are reusable instead of limiting ourselves to specific selectors?
It feels very limiting tying container queries to all contain attributes, too implicit.

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;
}

@kospl
Copy link

kospl commented Feb 16, 2021

I know you probably discussed this situation, but anyway

@container .media-object (width > 45em) {
  .media-object {
    width: 1px;
  }
}

@wUFr
Copy link

wUFr commented Feb 22, 2021

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 :)

@nhoizey
Copy link
Contributor

nhoizey commented Feb 22, 2021

@wUFr what you suggest looks a lot like viewport wide media queries instead of container queries.

If you replace .sidebar > ul:layout(desktop) with @media (min-width:900px){ .sidebar > ul { … } }, there is no need for container queries.

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/

@wUFr
Copy link

wUFr commented Feb 22, 2021

@nhoizey thinking about it, you're right, but it would be nice to have something like this too 🍺

@nhoizey
Copy link
Contributor

nhoizey commented Feb 22, 2021

@wUFr I agree, you could open another issue for this.

@matthew-dean
Copy link

Thanks @mirisuzanne for continuing to move this forward!

@MajPay
Copy link

MajPay commented Jun 16, 2021

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>

@matthew-dean
Copy link

@MajPay That wouldn't work in practice. A var() only has a value through the cascade. At the level of @container, it has no value, because --container-medium is only a value within :root within your stylesheet. So your usage is more like Less / Sass's uses of variables within a stylesheet, which CSS doesn't yet support (but hopefully will someday with being able to set global env vars).

@mirisuzanne
Copy link
Contributor Author

Please think about supporting css-vars inside the queries

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.

@MajPay
Copy link

MajPay commented Jun 21, 2021

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.

mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this issue Oct 14, 2022
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 <mstensho@chromium.org>
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#848577}
GitOrigin-RevId: 0ecdc725f612e6899bb452b74650101625bb9815
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Feb 2021 vf2f
Feb 11 earlier
Development

No branches or pull requests