-
Notifications
You must be signed in to change notification settings - Fork 10
2019 Proposal/Solution for Container Queries #12
Comments
So I'd like to give some reactions to this proposal.
These are the sorts of reasons why I think a workable solution for container queries or element queries is going to need to depend on something like CSS containment, which provides a much stricter boundary between what happens inside an element and what happens outside of it. Even then, I think if we loosen containment by providing single-dimension containment, we'd need to analyze carefully for risks of the problem in type (3) above. |
@dbaron Thanks for a well-thought-out, informed response! I'd been hoping to get some discussion on it.
This is probably the most on-point criticism, which is that querying the container is difficult because CSS layout algorithms are often not only both directions (parent -> child, child -> parent), but also sibling -> sibling, and often a combination of those. So, querying parent from child is not as trivial as I made it sound.
By this, do you mean some kind of syntax or property that isolates layout behavior to be single-directional, making queries predictable? Are there proposals for this? Would this be something like the "frameless iframe/webview" concept, where the sub-tree is isolated in some manner and can query it's own box like a standard media query? I'd love to help find a workable proposal, and I don't disagree at all with the potential pitfalls, so I'd love your thoughts on what kind of tweaks could make it work. |
I probably should have been clear that I meant the CSS Containment spec. It currently contains some of the primitives that I think are needed, but not all of them: it doesn't have any mechanism for having size containment only in a single axis. The idea that I was thinking of was the idea of a container queries mechanism that worked only on elements that have I think this idea has had some discussion in some other places as well, e.g., in w3c/csswg-drafts#3852 |
@dbaron Can you explain why element-relative units are a substantially larger calculation than the proposed |
So if I'm understanding things correctly, relative units are something that would take some result of layout of the parent element (or maybe some ancestor element) and apply it to the style of a child element (or descendant). An I think there are two differences:
|
Those Also, although achievable with mathematical functions, it'd be nice to have |
Before I go into details of this algorithm / solution, it's useful to establish some semantics.
First, there is some historical conflation of the terms "container queries" and "element queries". Depending on the solution (how someone thinks about the problem), these are often conflated to mean the same thing. There's discussion here about renaming the repo from "container queries" to "element queries" based on various conventions, etc, but I find that what's often missed when people talk about container queries vs. element queries is that people think they're talking about the same thing, when they often aren't.
To clarify:
Someone may say, "Yes, but isn't the container always an element?" In short, no. In some proposals, yes. In this proposal, no, and for (IMO) very good reasons.
The 2019 Container Queries Proposal
Summary
This algorithm / approach has the following:
What are we querying?
This is absolutely crucial, and what makes this query algorithm fast (as fast as media queries), deterministic, and single-pass.
What we are querying is the allocated width/height of the content box that this element will be placed in.
Immediately, someone may point out that the content box eventual dimensions can be affected by children. In order for this algorithm to be successful, that's why we must query what the content box is irrespective of children. In other words, what are the dimensions if no children were present?**
** Note: this doesn't mean we would measure as if the container were truly empty; specifically, an
:empty
selector would not apply during measurement if children are present, children just don't affect the allocated width/height for the purposes of this algorithm.Why the content box?
Just to briefly address this question, the reason why we query the content box specifically vs. the parent element's width is to avoid ambiguity / surprise when switching box models. Queries always refer to the "available space" to the child element, so that you can set properties of the child based on what pixels (or other units) will actually be available. Querying the parent element's actual (or calculated) width value would strongly bind the child element to the parent's box model. Therefore, the container must be a known constant: the content box dimensions of the parent before children are rendered.
Syntax additions
:container([MQ])
- selector query of the container, using MQ Level 3 or Level 4-style queries, and can query width / height [/ inline / block] (-axes)aw
/ah
/ai
/ab
- units relating to 1% of width / height / inline-axis / block-axis (semantically similar tovw
/vh
/vi
/vb
)Examples
So far, this is similar to most other container query algorithms. Where it differs is that this query is non-circular.
For example:
The
.parent
class, when laying out, has only been allocated a width of 50px. Therefore the allocated width of.parent
will stay fixed at 50px unless it is allocated a new width (not sized by children). (100aw
=50px
)Here's another simple example, assuming the same markup
In this example, because a float "wraps" around the child, and has no defined width to calculate irrespective of children, then the allocated width/height are both
0
(zero). Meaning the container query(min-width: 200px)
will not apply, and the background of.child
will bered
.We can make the container query apply by changing this to the following:
In the above example, the background of
.child
will beblue
.Other collapsing boxes
Other examples of boxes that don't have allocated width / height would be inline boxes (
display: inline-box / inline-flex / inline-grid
), or usingwidth: fit-content / min-content
. If they don't have awidth
/min-width
orheight
/min-height
value that can calculate an allocated value before the layout of children is determined, then those values will be zero.width: auto
Note that block boxes with a width of auto will have an allocated (non-zero) value. That is, an allocated width can be determined irrespective of children.
So, given a viewport of
1024px
, and this markup:In this case, a user agent stylesheet styles
<body>
as a block with a width of auto. Therefore the wrapper's container query applies and the background will bered
.An example with CSS Grid
Note that a container query applies to the pre-children content box of the parent. If you are slotting children into a grid, the queries of direct children of the grid will not be related to column / row slotting.
Meaning:
The above example helps make the important distinction between element queries and container queries. We're not querying the width that will be calculated for
.child
. We are strictly querying the initial content box of.parent
.However, we could easily nest children to "query" the width of the column for some powerful layout possibilities.
Given the following:
This would give you a layout like this:
Once
.grid-item
is slotted into a column, it has a calculated initial content box width that can then be queried by the.child
.Using
height
/inline
/block
Most of these examples have been querying width because of the way we typically layout pages, and because of default collapsing behavior. But, given a fixed (or minimum) height on a parent, it can also be queried.
We can also query the inline-axis or block-axis, depending on writing direction or layout settings.
Page / programmatic resizing
An important thing to note is that while the initial content box dimensions of an element used for a query can't be affected by children layout, it doesn't mean it's a "fixed" value per page load. A parent element may be resized by resize of the viewport (depending on initial width / height values) or changed programmatically.
On resize, the browser must determine if a parent element has a new initial content box size. In other words, just because the element is "resized", doesn't mean the initial content box size has changed.
This is best demonstrated by example. In the following example, no amount of "resizing" the viewport / browser window will cause the container query to match.
The initial content box width of parent is always
0
(zero), even though the.parent
element may be visibly resizing on-screen. (As an aside, this is an advantage overResizeObserver
-based polyfills of content queries, which are subject to circularity, since they respond to any resize of the element.)In the following example, however, resizing of the viewport will trigger matches / un-matches of the container query, and will dynamically apply those styles.
In the above example, note that container queries follow rules of the cascade. By default,
.child
has a background ofblue
. Once the available content width is200px
or greater, the background isblack
, overridingblue
. Once the available content width is400px
or greater, the background isred
, overridingblack
andblue
.Resizing with JavaScript
If the
.parent
has explicit dimensions set as inline styles with JavaScript, then the available content box width would be updated, and container queries that query the.parent
content box would be re-evaluated.Using allocated units
2019 Container Queries are extremely powerful, but have an important companion piece which is allocated units. This allows you to easily make your CSS styles more modular / adaptive regardless of container.
Allocated units (
aw
/ah
/ai
/ab
) are units relating to 1% of initial container box width / height / inline-axis / block-axis, respectively.Let's re-use our grid example.
Based on how much space
.child
may take, we can scale font-size to be reflective of that additional space.This would result in:
Of course, you can use a combination of
calc()
or newermin()
,max()
orclamp()
CSS functions (when available) to moderate the range / effect of allocated units.As a result, you can define individual, reusable "modules" that adapt to defined containers.
Note that
100aw
or100ah
etc. may resolve to0
(zero) if the parent content box has no intrinsic dimensions.Question to resolve: Would it be important to define a syntax for a default container width/height for a child element if a query returns a zero for either value? Or is
min()
/clamp()
sufficient? 🤔More advanced queries
A variety of examples of queries
Feedback
Feedback welcome. There are likely to be opinions™ around various points of this proposal, such as syntax. I think the selling points are, because this query algorithm is one-way, fast, and predictable, it's something that I believe could be implemented in browsers much sooner than previous proposals, mostly because of zero circularity. Layout / queries can be determined in a single pass, as quickly as media queries are today. This makes this a potential drop-in replacement for many/most media queries, since as noted by many other, smarter people, many people use
@media
queries when what they mean are container queries (the available width for my component).That said, there are obvious use cases of using both
@media
and 2019 Container Queries. I just didn't want to get too far in the weeds of creating use cases / examples, as this proposal was already quite long.The text was updated successfully, but these errors were encountered: