Open bfgeek opened 1 year ago
Separating masonry from grid layout was already discussed in #4650 and had a bunch of proponents, including me. @argyleink, @meyerweb, @AmeliaBR, @rachelandrew, @JoshuaLindquist, @thomascallahan FYI
As indicated by the different properties @bfgeek introduces, masonry layout works differently than grid in several points. And separating its syntax from grid also allows adding features that only apply to it and not to grid and vice versa.
Sebastian
I've never been a fan of including masonry in grid layout. Mostly because we'd forever more be having to figure out how new grid stuff works with Masonry layout, which will always be a bit weird because a masonry layout is different to a grid layout.
The current spec makes masonry layouts do a whole bunch of extra things that I've not seen authors ask for, mostly because "why not?" once it's all bundled into grid. Without compelling use cases for these features, it feels as if we'd be adding future problems of needing to work round masonry for grid additions, plus making to harder to add masonry specific things.
I think this proposal achieves the things people want out of masonry, and avoids these pitfalls.
(Fwiw I could have sworn we had a resolution to adopt column-reverse
and row-reverse
values for grid-auto-flow
, but I can't find it.)
(Fwiw I could have sworn we had a resolution to adopt column-reverse and row-reverse values for grid-auto-flow, but I can't find it.)
I was actually searching for this quite a lot lately and didn't find a resolution on this and to be honest not even a draft. Would that be smth. for the Cupertino F2F Agenda as well @fantasai?
So I don't forget - default alignment needs to be different for masonry as well. E.g. its desirable for replaced elements to stretch (vs. the grid default of start) similar to flexbox.
Another thing so I don't forget - variable track sizes is problematic with items that span multiple tracks with "dense" packing. To correctly place the item in the correct tracks, you need O(N) layout passes on the item.
Breaking the specifics out into separate issues (not that this invalidates this one, or that listing them all here isn't useful, but still should have them filed individually):
column-reverse
and row-reverse
were requested for Grid generally in https://github.com/w3c/csswg-drafts/issues/3622
- No ability to place a masonry item in an explicit row/column. I personally haven't seen people do this. Personally I don't think it's needed, and somewhat harmful accessibility wise.
For what it's worth, I do have a use case for that at https://www.gamestar.de. That page has two columns. And those columns contain different sections, which are placed explicitly in those columns. In horizontal direction the sections are meant to align on tracks while vertically they should be independent from each other. At the same time, they are explicitly placed in one of the tracks.
The two-column split is just one example. There are other pages which use three or even four columns.
So, this seems to be a perfect match for masonry layout.
And I believe, basically every website that is split into several columns could benefit from masonry layout. Take MDN for example. Instead of using Grid, its three-column layout could be done using masonry, i.e.
display: masonry;
masonry-template: minmax(0,1fr) minmax(0,2.5fr) minmax(0,15rem);
The point of using Masonry here is that the height of the items should not depend on each other nor on a grid.
So, I'd vote for a masonry-track
property that allows to place items explicitly in a track (or even span over multiple ones) similar to how grid-column
and grid-row
work.
Sebastian
That's not really masonry tho, right? They're not selecting the column based on available space; the stuff in the first column is meant for the first column, the stuff in the second is meant for the second.
Today this would just be a grid with two columns and one row, with all the stuff in each column wrapped in a container. To fix the general problem of wanting to flow a mixed set of contents into a single grid cell without having to pre-wrap them in a container div we have https://github.com/w3c/csswg-drafts/issues/9098
They're not selecting the column based on available space; the stuff in the first column is meant for the first column, the stuff in the second is meant for the second.
Right. That use case avoids the auto-placement algorithm altogether, like it can be done in Grid and Flexbox, already. The benefit over Grid is that they are only aligned on one axis and the benefit over Flexbox is that they don't rely on wrapping logic.
Today this would just be a grid with two columns and one row, with all the stuff in each column wrapped in a container.
Correct. That's how it's solved right now in the first example.
To fix the general problem of wanting to flow a mixed set of contents into a single grid cell without having to pre-wrap them in a container div we have #9098
Yes, that issues would also solve that use case. Though that use case seems to be a natural fit for masonry and less so for grid.
Sebastian
What I mean is, since there is no column auto-selection at all, this isn't Masonry. Masonry's entire reason for existing is to allow you to place items according to the tracks' current fill height; if you're skipping that entirely, then Masonry might not be the right abstraction for this. This is especially true if reusing Masonry as the layout abstraction for this (and making changes to accommodate it) would harm more core use-cases.
On the other hand, nothing about this case is particularly unusual for Grid. New functionality is needed (flowing multiple items into one cell as if they were grouped into a container element), but it's immediately compatible with the rest of Grid, too; it doesn't force us to reshape anything else about grid to accommodate it. As far as Grid is concerned it's exactly like just having a container element filled with stuff, and that's exactly what the use-case needs and wants out of it.
So yes, in theory we could address some variations of this "flow multiple items into one grid cell" use-case with Masonry, but we can't hit all of them, and even for the ones we can do, it requires adding additional features to Masonry that harm the core use-cases.
Masonry's entire reason for existing is to allow you to place items according to the tracks' current fill height
I'd argue that this is not true. The spec's introduction says this:
This module defines a layout system that removes that restriction so that items can be placed into Grid-like tracks in just one of the axes, while stacking them one after another in the other axis.
This is the main use case for Masonry. The auto-placement algorithm is one aspect of it, like it is for Grid layout. And restricting it to auto-placement seems arbitrary to me and is also hugely restricting its usefulness. Allowing to place items manually (in addition to auto-placement) opens Masonry up to a whole new class of use cases. The use case is not "flow multiple items into one grid cell". (That's handily expressed so that it fits to #9098.) The use case is "have multiple tracks in one axis and stack items in the other axis".
Sebastian
Yes, that's an introduction for Grid Level 3. It was written starting from the assumption that this new functionality would be based on Grid, so it being somewhat Grid-biased is not a surprise.
But the use-case in the wild, that we are attempting to capture in a layout mode, is pretty clear: it's items of arbitrary size being placed relatively tightly into tracks according to whichever track is currently least-filled. Placing items into specific tracks without automatic placement has not been traditionally called "masonry" or addressed by those tools; it's instead been handled by "group them in a container element".
Anything going beyond that remit might be appropriate to solve with Masonry, or might be best solved with another layout mode (one that already exists or one not yet written!). We should not assume that every nearby use-case is necessarily appropriate to fold in, particularly when, as I said, doing so would harm the core use-cases. (Which allowing different-sized columns might do, as has been argued, by making some features (spanners, in particular) either not work correctly or be surprisingly expensive to layout.)
The CSS Working Group just discussed Alternate masonry path forward
.
Regarding masonry-direction
: I'd like to propose that this is generalised to box-direction
and becomes a shared property that works for both Flexbox and Masonry layout (similar to how grid-gap
became gap
when support was expanded to Flexbox containers)
grid-gap having been generalized as gap for usage in flexbox actually made it impossible to detect support for gap in flexbox with @supports
(as the gap property was already supported by browsers implementing Grid with the new property name). So is it a good idea to generalize like that ?
grid-gap having been generalized as gap for usage in flexbox actually made it impossible to detect support for gap in flexbox with @supports (as the gap property was already supported by browsers implementing Grid with the new property name). So is it a good idea to generalize like that ?
Hmm... I think that would be less likely to be a problem here if this was included in the first version the masonry spec as you would be able to just test for masonry support at all. In general, I would prefer properties to be combined like this. Otherwise you end up with lots of duplicates which is much harder for authors to remember. Imagine if margin, padding, aspect-ratio, etc all had separate variants for each layout mode.
@nicoburns the issue is not about detecting support for it in Masonry, where the support should be detected for Masonry itself indeed. The issue would be detecting the support in Flexbox, in case box-direction
gets implemented by a browser implementing Masonry without adding support for it in Flexbox (leaving no way to detect the missing support of Flexbox with box-direction).
Properties shared between multiple layouts would require these rules to be feature detectable properly:
However, I have no idea how realistic enforcing those is.
margin
and padding
were there before the introduction of the various layout modes (at least the ones that came at a time where compatibility with @supports
makes sense), so supporting them was part of the initial spec of those layouts.
And aspect-ratio
was specified taking into account its impact on all layout modes, allowing to follow the rules above.
in case box-direction gets implemented by a browser implementing Masonry without adding support for it in Flexbox (leaving no way to detect the missing support of Flexbox with box-direction).
I mean, ideally this just wouldn't happen, and the release of the property could be coordinated for all layout modes (similarly to aspect-ratio). But in this case, I don't think it would matter too much if that didn't happen:
The issue would be detecting the support in Flexbox, in case box-direction gets implemented by a browser implementing Masonry without adding support for it in Flexbox (leaving no way to detect the missing support of Flexbox with box-direction).
What would be the use case for this? If you want to support older browsers that don't support box-direction
with flexbox then you would need to set both box-direction
and flex-direction
. And there would be no point in doing this conditionally which would just add more code to do the same thing (as CSS will ignore properties it doesn't understand anyway).
If there were properties that did require layout-mode specific feature detection then I guess it might make sense to add the ability to specify display mode to @supports
somehow.
We flagged this for the agenda in order to have a discussion of the pros and cons of building masonry into Grid vs creating a new display type, and hopefully resolve on one direction or the other. There's a multitude of different considerations, but some of the key ones have been outlined above and in https://github.com/w3c/csswg-drafts/issues/10233; they include
This issue about the overall direction of the layout model is blocking FPWD and general progress on this feature.
The Microsoft Edge team has spent time investigating the current Masonry proposal as outlined in the Grid specification, and we have compared this with the proposal Ian has laid out in this issue. In summary, moving Masonry to its own display type would be our recommended path forward for the reasons outlined below.
The first reason is that we believe there are core aspects of Masonry that are philosophically distinct from Grid:
As defined in the Grid spec, placement must come before sizing. Masonry breaks this principle by requiring the auto placement algorithm to have the resolved contribution size of a grid item before placing it.
In Grid, the grid areas are defined by the intersection between grid lines in the columns and row axis. However, in Masonry the grid lines are only honored in the grid axis. In the masonry axis, there are no grid lines; these are instead determined by the “running position” of each track in the grid axis.
Given points 1 and 2 above, the implementation of Masonry within the Grid Module will require large chunks of divergences in code, which helps indicate to us that Masonry makes more sense as its own display type.
Subgrid is overkill for Masonry layout.
display: masonry
) in the grid axis. There is at least one feature in Grid that doesn't make sense in Masonry (grid-template-areas
) and one feature in Masonry that doesn't make sense in Grid (masonry-auto-flow
).
The next major category that we find to be a drawback to keeping Masonry as part of the Grid spec is the performance implication. The proposal outlined to address the placement/sizing mismatch in https://github.com/w3c/csswg-drafts/issues/8206#issuecomment-1710169743 could result in approximately quadratic performance, even in very simple cases.
Webkit’s initial attempt to implement this proposal (Masonry isn't performing intrinsic sizing correctly. by stwrt · Pull Request #27048 · WebKit/WebKit (github.com)) doesn’t, from our reading of the code, implement this in a way that showcases the full performance implication. For example, items that span multiple tracks don’t seem to be considered in Webkit’s implementation.
More specifically, consider grid-template-columns: 100px auto auto auto 50px
, an item with width: 100px
spanning 3 columns has 3 different possible contributions to the 3rd track. If it's placed at the beginning of the 1st track, it will contribute 0px
to the 3rd column; if it's placed at the beginning of the 2nd column it will contribute 33.33px
; finally, if placed at the beginning of the 3rd track, it will contribute 25px
.
Beyond the complexity of implementation, we also feel that Ian’s proposal would be much simpler from an authoring perspective, likely will be easier for authors to understand, while also considering what we believe will be top authoring use cases.
As such, we would like to +1 the proposal to move Masonry to its own display type.
@ethanjv
As I did outline in my comment in https://github.com/w3c/csswg-drafts/issues/10233, I will need to find more time to elaborate on why I think masonry could still be a part of CSS grid (but maybe in a less magical form), and how there are many non-masonry cases for one-dimensional grids.
For now, I want to quickly respond to two points:
There is at least one feature in Grid that doesn't make sense in Masonry (
grid-template-areas
)
There are multiple use cases for template-areas in one-dimensional grids, I did write an article about one of them: “Grid Projection Naming”. If anything, having an ability to have one of the dimensions removed from a grid could allow for more flexible and usable grid-template-areas
for such grids.
More specifically, consider
grid-template-columns: 100px auto auto auto 50px
, an item withwidth: 100px
spanning 3 columns has 3 different possible contributions to the 3rd track. If it's placed at the beginning of the 1st track, it will contribute0px
to the 3rd column; if it's placed at the beginning of the 2nd column it will contribute33.33px
; finally, if placed at the beginning of the 3rd track, it will contribute25px
.
I really think that spanning items should not contribute to the width of the columns in masonry. Even in a regular grid this is the behavior that I find unnecessary and often harmful, and I don't think there are many legit use cases for wanting this in a masonry layout. As an author, when I want some element to span other columns/rows, I want it to adapt to whatever there is, not to contribute. (After writing this, I found https://github.com/w3c/csswg-drafts/issues/10053 which talks about this point, I'll copy this part there.)
@alisonmaher A few quick points:
[...] This is significant implementation-wise because determining alignment in the masonry axis will require a separate set of data structures to compute the running positions and perform layout than is needed in Grid today.
Given points 1 and 2 above, the implementation of Masonry within the Grid Module will require large chunks of divergences in code, which helps indicate to us that Masonry makes more sense as its own display type.
How you split up your code and how we split up display
do not need to match (and in many cases don't). That's an implementation detail. Whether we go with "masonry in grid" or "masonry as separate display type", an implementation could choose to share the implementation in their GridFormattingContext module or split out into separate GridFC/MasonryFC modules. This isn't something that should play into our decision here, which should be guided by what's the best interface for authors.
- There is at least one feature in Grid that doesn't make sense in Masonry (grid-template-areas) and one feature in Masonry that doesn't make sense in Grid (masonry-auto-flow).
This is true, but I think those two properties aren't it. :) grid-auto-rows/columns
and masonry-threshold
are the two properties that don't cross over; grid-template-areas
works fine and is usable in Masonry, and there's an issue open (https://github.com/w3c/csswg-drafts/issues/10231) on merging masonry-auto-flow
and grid-auto-flow
which may make that point moot.
tldr: We think the performance characteristics of the current Grid-based Masonry spec are unshippable, and more or less unfixable while the two layout modes are entwined. But we think we can get a lot closer to agreement on the feature set than it appears the editors currently believe, if we can agree to make this a separate display type. In particular, every single one of the examples in the recent Safari blog post is completely fine in our preferred approach (despite the blog post asserting they aren't, because it's arguing against a much simpler version). See the very end for our alternate proposal; the rest of this is arguments for why we don't like the current and want something different.
The "masonry" behavior is currently defined directly on top of Grid, as a variant behavior similar to "subgrid". We (the Chrome and Edge team's engineers) find the feature valuable and want it to progress, but have come to the conclusion that this is not the right way to define this behavior.
Some of these issues were brought up back in 2022, in issue 8206. In July 2023, in Issue 9041, we raised the concern more generally, and suggested an alternate approach. Since then we've opened several additional issues about more specific concerns with the current approach (or had them opened, spinning out specific concerns from 9041):
These issues can generally be reduced to a single conflict: at their most fundamental level, Grid and Masonry are opposite wrt sizing and placement. Grid places all items before layout, and then has complete knowledge of what items are in any given track, so it can do complex intrinsic sizing based on that knowledge. Masonry places items as they're laid out, and thus it cannot know what elements will end up in any given track, and can't do the same complex intrinsic sizing (but as a result gains the potential for some behavior that Grid can't easily accommodate).
fantasai has attempted to address these concerns, by defining special "pre-placement" layout behavior - laying out each item in every possible position to obtain intrinsic measurements, and then using those measurements to size the tracks before starting placement. However, this doesn't fundamentally fix the issues, it just changes what the behavior is quadratic on: rather than being quadratic on the number of items, the complexity scales with (number of items) * (number of tracks). The exponential behavior from nesting grids also remains under this behavior. Also, this is still fundamentally different from what Grid does, raising the question of why they're considered the same layout mode when they use such different layout algorithms.
Given that we know authors like to push the boundaries of layout modes, and in particular often create grids with a very large number of tracks (sometimes to produce something like Masonry layout, but not always), we don't think this fix is acceptable. We extra don't believe exponential layout behavior is acceptable, as it causes layout to visibly stutter quickly, with only a handful of reasonably-sized grids involved. All of these can be avoided with some reasonable restrictions that we think still allow a wide range of useful Masonry behaviors, but which would be awkward to apply in the general Grid framework.
Additionally, as stated in Issue 9321, we think there are some useful behaviors that Masonry can allow that are infeasible to apply to Grid, such as repeat(auto-fill, auto). (Doing so in Grid causes essentially the same issues that were described for Masonry, above.)
Finally, we have a general design objection to attempting to merge Masonry and Grid into a single layout concept. The two layout modes absolutely have many conceptual similarities, and we can and should lean on those to ensure that they can work well together in obvious ways, but each has enough unique peculiarities that it just gets awkward to define what works for Grid, what works for Masonry, and what works for both. I believe this situation to be similar to that of Block and Multicol - these were folded into a single layout mode, and ever since we've had to deal with odd inconsistencies between the two in what behaviors they expect. If we had defined display: multicol back in the day, many issues would have been avoided. I think the Grid/Masonry marriage is even more fraught with inconsistencies - heck, they fundamentally don't and can't share a layout algorithm - and would like to avoid us repeating mistakes we made in the past. As we move forward, every new addition to Grid and Masonry will have to be evaluated to see if it applies to one or both modes, and when it applies to both, what distinctive quirks it has in each.
So the above is our general objections to the current approach, and address fantasai's first and third bullet points (whether Masonry fits in Grid conceptually, and whether integrating the two models is a pro or a con). I'll go a little more into detail about these in a sec, but first, let's discuss their second bullet point (whether track size variation is needed/useful, and how we can deal with the perf implications).
As we've stated before, existing JS masonry implementations don't generally seem to offer the ability to do distinct track sizes - all tracks the same width seems to be the overwhelming common case. That said, having differently-sized tracks does seem reasonable; Jen's blog post on Friday had a bunch of good examples.
There's a lot more flexibility between "all tracks the same" and "any intermixing of tracks like Grid can do", tho, and we think there's a reasonable sweet spot that allows a lot of useful cases without running into the perf issues we find so objectionable.
Basically, the perf issues arise only when you mix an intrinsically-sized track with any different-sized track.
In other words, every example given in Jen's blog post is just fine. Those are all totally doable, no objections here. We just object to the possibility of writing, say, grid-template-columns: 50px auto 100px;, because in degenerate cases (which are more common than one would suppose), they cause quadratic or even exponential layout behavior.
Now back to bullet point 3 - is integrating the two models a pro or a con.
We think the cons outweigh the pros. There are simply too many places where some property is only valid for one or the other, or worse, some values for a shared property are only valid for one or the other. Above I outlined why a perfectly valid grid-template-columns would be problematic for Masonry, but on the other side, Masonry is perfectly fine with allowing masonry-tracks: repeat(auto-fill, auto), which is so disallowed in Grid that it's a syntax error. These sorts of syntax distinctions are easier to understand and remember when they're across similar but separate properties; when it's a single property that just has a different mix of valid values depending on the value of a second property, it gets confusing. And we don't think it's reasonable to expect authors to develop an intuitive understanding of this; the reasons for some values being usable in Grid vs Masonry depend on fairly deep layout-engine knowledge that we don't generally expect authors to possess.
(We'd also have to define what happens when they do write the wrong thing. We can't reject it at parse time, since its validity depends on the computed value of another property. We'd have to guess at some fallback behavior. This is already present in the spec, with auto-fit being invalid in Masonry, and treated as auto-fill instead. It would be better if this could be rejected at parse time.)
Both layout modes have properties that don't apply to the other: grid-auto-* for Grid, masonry-threshold (and masonry-auto-flow, if we keep that).
Worse is grid-template-areas, which could conceptually apply to Masonry (giving names to tracks or groups of tracks), but can't in practice because of the 2d nature of the syntax. We'd have to define a masonry-areas to port the concept over.
The end result is that authors have an arbitrary-feeling list of restrictions in what properties can be used for one or the other - properties with a masonry- prefix obviously only work for Masonry, but some grid- properties work too, and others don't, and for the ones that do work, sometimes only certain values work. If they were more cleanly separated layout modes, with all the Masonry properties having a masonry-* prefix even when they're similar to Grid properties, none of this is a concern.
Finally, back to bullet point 1, whether Masonry and Grid fit together conceptually. We agree that they do! Many concepts are shared between them, and Jen's example of subgrid in masonry looks completely reasonable. We think that allowing subgrid items to take on a Masonry parent's lines is perfectly reasonable, with the caveat that the subgrid items interact with the Masonry track sizing in a Masonry-like way (as described above, for the case where the Masonry is using a repeated intrinsic track size; when the tracks are fixed/flexible, the issue's moot already). Similarly, if we defined some sort of "submasonry" value, using those in Grid parents would be reasonable, with similar restrictions on how they interact with Grid sizing.
And generally, for any concept for which Grid and Masonry share the idea, they should both have properties for it, with as identical of a syntax as possible. Helping authors transfer knowledge between the two layout modes is absolutely a good idea.
So, long post, apologies, we're at the end. Here is a very rough sketch of the proposal as we'd prefer to see it. I'm happy to spend time putting together a UD for this if it's easier to review that way, I just didn't have time to do so.
/* on the masonry container */
display: masonry;
masonry-template-tracks: [ <line-names> ? [ <fixed-or-flexible-size> | <fixed-or-flexible-repeat> ] ]+ <line-names>? ]
| [ <line-names>? <intrinsic-repeat> <line-names>? ]
masonry-template-areas: (like one row of grid-template-areas)
masonry-direction: row | column | row-reverse | column-reverse
/* initial value column, contrasted with flexbox's initial row */
justify/align-*: (same as Grid, but with some restrictions depending on masonry-direction)
masonry-threshold: [(as discussed in 9328)](https://github.com/w3c/csswg-drafts/issues/9328)
masonry-auto-flow: (as in the current spec)
/* on the masonry items */
masonry-track: <grid-line> [ / <grid-line> ]?
masonry-track-start: <grid-line>
masonry-track-end: <grid-line>
justify/align-*: (same as above)
We're flexible on all the details, save for those I talked about in the preceding text of this comment.
grid-template-areas works fine and is usable in Masonry
I don't agree that it works "fine". It works, in theory, but it's awkward to use and allows you to write things that don't make sense in a Masonry context.
"awkward" because you have to do something completely different if the masonry has columns vs rows. When it's columns, you just write grid-template-areas: "foo bar baz";
, easy, but when it's rows you have to write grid-template-areas: "foo" "bar" "baz";
.
Similarly, and perhaps worse, if you use grid-template
so you can size and name the tracks at the same time, the sizing values go in completely different places, as does the masonry
keyword: grid-template-areas: "foo bar baz" masonry / 10px 20px 1fr;
vs grid-template-areas: "foo" 10px "bar" 20px "baz" 1fr / masonry;
.
And "don't make sense" in that you can write grid-template-areas: "foo foo" "bar bar";
to define a 2x2 explicit grid, but Masonry has, at best, an Nx1 (or 1xN) explicit grid. We'd have to ignore all but the first track in one of the axises, rather than being able to reject that at parse time as a syntax error.
and there's an issue open (https://github.com/w3c/csswg-drafts/issues/10231) on merging masonry-auto-flow and grid-auto-flow which may make that point moot.
I think this would, similarly, be a bad idea. Masonry and Grid have slightly different things that they care about for auto flowing controls; if we put them both in the same property, we either have to bodge the different behaviors into the same keywords even when they're not ideal, or define layout-specific keywords and give them some fallback behavior when they're specified for the wrong layout. Again, being able to reject bad syntax at parse time is better.
How you split up your code and how we split up display do not need to match (and in many cases don't). That's an implementation detail. Whether we go with "masonry in grid" or "masonry as separate display type", an implementation could choose to share the implementation in their GridFormattingContext module or split out into separate GridFC/MasonryFC modules.
Sure, this is an implementation detail. However, the implementation details we called out are a direct reflection of how the spec for Masonry is designed today. If we are having to diverge on very core aspects of an algorithm, the point still stands that this is a good indication that they are distinct enough concepts to separate (whether or not there are other concepts we can reuse between the two).
This isn't something that should play into our decision here, which should be guided by what's the best interface for authors.
Implementability should be discussed when the implementation is derived from the algorithm outlined in a spec. And the performance implications in particular would have a direct impact on authors/end users.
This is true, but I think those two properties aren't it. :) grid-auto-rows/columns and masonry-threshold are the two properties that don't cross over; grid-template-areas works fine and is usable in Masonry, and there's an issue open (https://github.com/w3c/csswg-drafts/issues/10231) on merging masonry-auto-flow and grid-auto-flow which may make that point moot.
Gotcha, yeah these might be the right properties to be discussing, and I'm glad to hear it is something we are looking into. However, given that that issue is not resolved, it is still a valid point to consider when discussing the larger question at hand in this issue.
The CSS Working Group just discussed Alternate masonry path forward
.
Implementability should be discussed when the implementation is derived from the algorithm outlined in a spec. And the performance implications in particular would have a direct impact on authors/end users.
Yes, these are valid things to consider: whether it can be implemented, and what performance characteristics it has. But whether it ends up being one module or two in your implementation... that's not an important consideration for whether it should be one display type or two. How you organize your code is not a consideration for how CSS should organize its interface for authors.
I mentioned a few friction points when trying to do Masonry in Grid, but didn't go into great detail. Here's a more complete list ^_^
grid-template-areas
allows you to specify an N×M grid in Grid, but Masonry is a 1×N or N×1 grid. We can't reject too-large grids at parse time, so we'd have to specify that all but the first column/row are ignored when doing Masonry.
Masonry is activated by setting one of grid-template-rows
or grid-template-columns
to masonry
. Unlike subgrid
, it's not valid to set it in both. We can enforce this syntactically in the shorthands, but an author can still manually set both properties to masonry
. We then need to decide how to handle this error case.
grid-template
is written substantially differently when defining an N×1 grid vs a 1×N grid. For example, to define three masonry tracks, you'd write either grid-template: "foo bar baz" masonry / 10px 20px 1fr;
for columns, or grid-template: "foo" 10px "bar" 20px "baz" 1fr / masonry;
for rows. (If you're manually specifying additional line names it gets even worse.) This seems pretty bad for authors to have to keep in mind!
(Note: I do not think it's important to optimize for easy switching between a row and column masonry layout; it's perfectly fine to require people to change from a *-rows
property to a *-columns
one. What I'm objecting to here is the property actually having a meaningfully different syntax depending on the direction.)
Grid and Masonry allow different sorts of track sizing. 10px 20px 1fr
is a valid set of track sizes for both. 10px auto min-content
is a valid set of track sizes for Grid, but illegal for Masonry (per our argument in 9041). repeat(auto-fit, 10px)
is also valid for Grid, but illegal for Masonry (per the spec, and for good reason). repeat(auto-fill, auto)
is valid for Masonry (and very useful), but illegal for Grid.
If they're both done in grid-template-rows/columns
, we'd have to define what happens when you specify an "illegal" set of track sizes, and there's no good answer. It would be better if we could reject these at parse time, but that requires separate properties.
grid-auto-flow
doesn't do anything in Masonry, and masonry-auto-flow
doesn't do anything in Grid. If we accept #10231 and merge the two together, then we have the same situation as the last bullet point - some values are valid in both, but others are valid in only one or the other, and we have to define what happens when you specify an "illegal" value.
grid
also integrates grid-auto-flow
into its syntax, in a way that isn't meaningful for Masonry (the auto-flow
keyword sets grid-auto-flow: rows/columns
, which are meaningless in Masonry). We'd either have to make the grid
syntax even more complex (and, again, still define what happens when you use the version that tries to set grid-auto-flow
, and vice versa for using the Masonry version in a normal Grid), or just not let Masonry set its auto flow values in grid
at all.
Grid has four placement properties (and two props that merge a pair, and one prop that merges all four), setting the position of each edge of the grid item. Masonry can only set two of these; the other two are determined by the flow. This means we have to ignore three of the properties (two individuals and one pair) and part of a fourth (two of the values in grid-area
, potentially) when a container is in Masonry mode.
Grid can use all six of the justify/align-*
properties, but Masonry uses only a subset (matching Flexbox, but hopefully without repeating the mistake Flexbox made of using main/cross axis rather than block/inline). So both justify-content
and align-content
work, but apply to different things (either aligning the tracks in the container, or the items in each track), but only one of justify-self
and align-self
work (and similarly for justify-items
/align-items
), depending on the track direction.
(At least, I'm supposing that the *-content
properties work like Flexbox, because that seems useful, rather than like Grid, treating the masonry axis as just a single track. The *-self
properties definitely have to act like Flexbox, tho.)
Conversely, if Masonry is a separate layout mode with masonry-*
properties, you do have some degree of repetition, but in every case, the value spaces are somewhat different, so having the distinct syntaxes be distinguishable at parse time is a good thing.
masonry-template-tracks
overlaps with grid-template-rows/columns
, but with different sets of allowed track sizes.masonry-template-areas
overlaps with grid-areas
, but with a slightly distinct syntax (masonry only allows one set of names, and uses the same syntax for either direction)masonry-template
and masonry
overlap with grid-template
and grid
, but with both of the differences listed above and more besides.masonry-auto-flow
overlaps with grid-auto-flow
, but with a different set of allowed keywordsmasonry-area
overlaps with grid-area
, but with a slightly reduced syntax (only 1 or 2 values allowed)I think that, in every one of these cases, the benefit from having their differences being detectable at parse-time is worth more than the downside of having their overlap be duplicated between the two properties.
I talked about this on the call, but when I first starting showing people flexbox and grid, a very frequent comment was that they behaved inconsistently. What they meant was that they behaved differently to their assumptions about how things behave in block layout. At this point, I think we've been able to get a reasonable understanding of what it means to change formatting content across to developers. For example, to understand why certain alignment properties don't apply in flexbox.
If we bundle masonry with grid, I fear we end up with a situation where we have to say "these properties/values work in a grid formatting context but not if you are using masonry" or "these only work if you are in a grid formatting context AND using masonry". That's not just things that exist today (such as the alignment properties), but anything we might want to add in the future.
As Tab mentioned above, we can reuse a lot of grid things in display: masonry
, we don't lose all the things Jen has shown in her demos because we define it as a new spec, but we also don't end up dancing around the differences forever.
+1 to Tab's and Rachel's comments.
A theme I saw in the developer feedback thread is that integrating masonry into grid would build on existing knowledge of grid syntax and simplify some fallback cases. I agree that, for an author who has mastered CSS Grid, switching in one masonry
keyword is simpler than having to rename declarations and add an @supports
for fallback. I don't see anything in Tab's proposal that would hinder the knowledge transfer at a conceptual level, though.
I'd like to advocate instead on behalf of new authors who want to jump straight into building a masonry layout. Which of the following would represent the lowest barrier to entry?
In my opinion, the benefits of 3 to new authors in setting aside advanced Grid features until they're needed, outweigh the inconveniences to experienced authors, who presumably are already used to dealing with different property names for different contexts and having to implement graceful fallback.
One option could be to selectively merge style properties. It is my opinion that merging has worked well in the past in some cases (gap
working across both Flexbox and Grid being a good example) but badly in others (IMO the attempt to abstract away axis by using "align" and "justify" for alignment properties was a mistake: this is something which frequently trips up people I try to teach web layout to and even turns them off authoring for the web entirely and I think clearer property names using "block", "inline", "main", "cross", "rows", "columns" would have made a big difference).
We could for example rename grid-template-columns
to just columns
, indicating that it applies across both classic Grid and Masonry contexts. While leaving grid-template-areas
as it is, indicating that it applies only to grid. Perhaps that then nudges us towards a separate display: masonry
value. Although I think is possibly less important. There is certainly precedent for multiple similar display modes using separate keywords though (e.g. block and flow-root).
(speaking as a member, not the chair)
On the point of making it easier to switch between Grid and Masonry at different break points, I am not convinced that keeping Masonry in Grid helps this. I expect that various track, template and area settings would likely also need to be switched at the break points, so you would be setting several properties in your break point queries.
If there is a separate set of masonry- properties, you could set everything up for both masonry and for grid, and just have your query do the display switch. Tweaks on the separate grid- and masonry-* properties would only affect one layout and not need to be tested on the other. I think this might be more helpful overall than where it would be useful (or possible!) to use the same property value for both Masonry and Grid.
I would also like to strongly advocate for the performance characteristics (and specifically of the default or easy to use styles) being strongly weighted in the debate (to the point that poor performance should be considered a deal breaker).
Performance is already a problem for CSS layout (with both Flexbox and CSS Grid having exponential complexity (albeit reducible with caching) unless one goes out of one's way to use styles like min-width: 0px
on all flexbox items and minmax(0, 1fr)
instead of 1fr
for grid tracks). And I would like to see this fixed rather than new performance issues introduced. If that means intrinsic sizing of masonry columns being excluded entirely then so be it.
@nicoburns Merging gap
worked because flex-gap
and grid-gap
were identical; absolutely zero differences in syntax. It was solely a difference in property name. Same for the *-self/items/content
properties; exactly which set of properties apply varies between layout modes, but when they do apply, they use the same values. (I was skeptical of this when @fantasai first suggested merging the Flex and Grid alignment properties, but I was wrong!)
(Agreed on the naming, tho - we made a mistake in Flexbox in making it too flex-direction centric rather than just sticking with logical directions, and that hampered our ability to use logical-direction names. They absolutely should have been block-align-self
/etc.)
However, as I pointed out at the end of my previous comment, this isn't true for Masonry/Grid. Almost none of the overlapping properties have the same syntax - as far as I can tell, the only ones that do are the placement properties (grid-row
, etc), minus grid-area
. Every single other overlapping property has some syntax space that's valid only for Grid, or only for Masonry, or both.
I think it's worth strengthening @tabatkins' point about ability to accept/reject at parse time. This means that dev tools will be able to suggest/highlight values immediately, and greatly improves discoverability. It would be nice if new syntax we add is statically checkable, and it may reduce friction.
Regarding my comment on animation at the meeting, I guess we could always have View Transitions as last resort.
(chair hat off still, this is just me trying to make sense of the options)
Assumptions and caveats:
Masonry + Grid together
Pros:
Cons:
Masonry as a separate display type
Pros:
Cons:
By “new properties” I mean things that aren’t yet in Grid or proposed for Masonry. If the new thing works identically in both modes then the property duplication for the separate display types is annoying. But it’s also not great if a new single property has to have a mode switch designed into it.
From a “designing future extensions to grid/masonry” standpoint I think I am favoring a separate display type.
New properties must be designed to accommodate both modes
And this must be done when new properties or values are first introduced. We can't add something new for Grid and only later figure out its use for Masonry.
And this must be done when new properties or values are first introduced. We can't add something new for Grid and only later figure out its use for Masonry.
Would also make it hard for feature detection, remember gap
detection with display: flex
.
When using dedicated properties for masonry, you can feature detect support for a certain value separately from grid supporting it.
More complicated value spaces (some apply only to one mode)
And just to stress on this point - every single Grid property that can apply to masonry, save for grid-row/-start/-end
(or -column
, depending on direction) has this issue. They all have some values that are only valid for one or the other type. (Documented at the end of this preceding comment.)
+1 to Tab's and Rachel's comments
@mirisuzanne brought up a good argument in favor of masonry in grid: you might want to have a masonry in a certain @media
query breakpoint and a grid in others, while sharing the same tracks. With a separate display type, you have to duplicate all the properties for each breakpoints.
@nt1m That's possible, but I'm less certain how plausible it really is. Presumably you'd be shifting from an auto-flow grid into a masonry; I'm not sure I really see how common it would be to change from "all rows same height" to "no rows" based on a breakpoint. If you are changing based on a size breakpoint, it seems more likely to me that you're also going to be changing the tracks, in which case it's the same effort either way.
Hi, I need any HTMl example of HTML code related to article so I can better understand while I am working HTML conversion by Figma Is it possible to get any working example code to make it easier to understand for future use??
I hadn't noticed the limitations that grid would have in conjunction with masonry, but that alone is a very compelling argument to have masonry as its own display. I think having masonry as a separate display would keep clarity between all of the display types as well, given that they do all seemingly handle different dimensionality. Given this method vs the Webkit method I'd prefer this. Thanks for the thoughtful breakdown!
(chair hat on)
In the interests of arriving at a decision, is there anything in the conversation so far that convinces the “separate display type” folks that integrating with Grid is the better path forward, or that convinces the “integrate with Grid” folks that a separate display type is the better path forward?
(bonus points if equal numbers of people switch camps)
For what it's worth, I was initially neutral but the reasonings provided by the "separate display type" folks have convinced me that option seems better.
Quoting @astearns:
Masonry + Grid together
Pros: No duplicate properties for similar sorts of things You can reuse most of what you know about Grid in Masonry
Cons:
More complicated value spaces (some apply only to one mode) New properties must be designed to accommodate both modes
Masonry as a separate display type
Pros:
Less complicated values (each mode has only what it can use) New properties are less complicated to add
Cons:
Duplicate (or very similar) properties need to be added New properties can add to the duplication
Stepping away from the discussion a bit, I feel like this:
Less complicated values (each mode has only what it can use)
is the most important argument of all. Masonry has no value at all if developers don't adopt it, and I feel like the chances are much higher with a strictly defined syntax. As a speaker and someone who gives workshops on a regular basis, I still run into people who don't understand grid and are still only using flexbox. Blowing up the grid spec even more would exacerbate this problem.
New features (afaik) are introduced for the users - the web developers - and therefore should have the main focus on them to help solve their problems and therefore come with a good UX (Web Developer Experience). Going with Masonry + Grid together feels like we are shifting towards a better DX (Spec Writers - Browser Vendors) at the expense of worse UX by increasing complexity by introducing new edge cases into the spec and thus decreasing the adoption rate.
I feel like I was kind of repeating myself, but I hope you get my point - Take a step back and don't forget who we're introducing this feature for.
@Que-tin
I still run into people who don't understand grid and are still only using flexbox. Blowing up the grid spec even more would exacerbate this problem.
You could also argue that this is due to Grid being an entirely new thing for them to learn, in which case keeping Masonry separate only adds to that problem?
For me, this all boils down to the question of whether CSS syntax should be based on "What" it's doing, or "How" it's doing it.
When I say background: black
, then switch it to background: linear-gradient(45deg, black, white)
, it doesn't matter to me HOW the browser is implementing that instruction... I’m simply telling it what I want it to look like. If the browser has to switch to an entirely different rendering approach to implement that instruction in a performant way, that’s a problem for the browser dev, not the CSS dev.
Creating display: masonry
as a totally separate thing because of implementation concerns feels like it goes against this principle... it’s making me think about HOW the browser will perform my instructions, rather than WHAT I want it to do.
However I can appreciate the arguments on both sides, and ultimately I care more about getting SOME form of this I can use than I do about getting perfect syntax. Heck, the flexbox syntax* still confuses me (I never know whether I want justify or align) but I’m still really glad we have it.
*Possibly more a reflection of my lack of intelligence than anything else. To those of you who made it happen: thank you.
TL;DR
The primary issue with building masonry layout on top of grid is related to intrinsic-sizing (of the container itself, and intrinsic tracks).
Grid layout works by placing everything in the 2D grid, that is assigning each child a column(s), and row(s), then sizing the grid with all the children fixed within the grid (they can't jump to another grid position).
Masonry layout however works in reverse - by sizing the rows/columns first, then placing children in the "shortest" row/column. This means that we can't correctly size the rows/columns (as we don't know the content), and also can't size the container itself correctly (if sizing using min/max content sizes).
This is detailed in issue: https://github.com/w3c/csswg-drafts/issues/8206
There are potential workarounds to deal with this issue, e.g. assume that all children are in every row/column for the purposes of sizing, but this prevents some potentially desirable use cases.
One thing that seems desirable is to allow a wider/different syntax for rows/columns than is currently allowed for grid, e.g.
masonry-template: repeat(auto-fill, auto)
. (Above would measure all the masonry items, and select the best number of tracks to fit the content). (Arguably above might be a better default than masonry-template: auto for example). This isn't possible for grid-template for good reasons - but we could accept it for masonry. One open question is if we need different track sizes or just one would suffice. All the designs I have personally seen have just one track repeated N times. Accepting just one track template would allow easier intrinsic sizing of spanners for example.One addition which is currently missing with grid repeaters is the ability to clamp to a minimum / maximum number. This is more relevant with masonry. E.g.
masonry-template: repeat(auto-fill, /* min */ 1, /* max */ 5, auto)
would allow clamping to a maximum of 5 tracks which seems desirable from designs I've seen. (this is probably a bad syntax but you get the idea).Another missing in the current proposal is controlling the direction of the masonry flow. E.g. there are cases where you'd want the masonry axis to start from the block-end / inline-end. This could be covered by a property similar to flex-direction , a simple (and likely understandable property) might be:
masonry-direction: row | row-reverse | column | column-reverse
(this would be similar to the originTop/originLeft controls in masonry.js https://masonry.desandro.com/options.html#originleft )One issue with masonry style layouts is that things can easily be visually out of order, e.g. if the current tracks are [100px, 99px] the next masonry item would be placed in the 2nd track, when the first would be more natural. A potentially solution to this is some user defined "threshold" to "place within the first track within Xpx of the smallest track"
masonry-threshold: <length>
Things that aren't in this proposal vs. the current draft are:
Ian