Anchor Positioning Is Disruptive
New layouts will be possible
The more I play with it, the more convinced I am that anchor positioning is going to unlock some surprising new layouts.
What makes something a ‘grid’, and what’s at stake?
Back in 2020, Firefox released a prototype for doing ‘masonry’ layout in CSS. Now all the browsers are eager to ship something, but there’s a hot debate about the best syntax to use.
Since posting this article, Safari has responded to the debate with (I think) a fairly strong set of arguments. I’ve added my thoughts below.
The Firefox prototype
and CSS Grid Level 3 specification
initially introduced a masonry
keyword
as part of CSS grid layout.
We can define a standard grid template on one axis,
set the cross-axis to masonry
,
and we get a ‘waterfall’ of content
divided somewhat evenly across our tracks –
aligned on one axis,
but packing more densely on the other.
At its core, a ‘masonry’ layout works like ‘grid’ layout on one axis and ‘flexbox’ on the other. Jen Simmons – then at Mozilla, but now working for Apple – developed a great demonstration of both the new functionality and several alternative techniques:
Rachel Andrew – then independent, but now at Google – immediately pushed back on the proposal, suggesting that masonry and grid are different enough they should not be part of the same layout mode. Since then, the debate has heated up with conflicting proposals from Apple and Google:
Based on the comment threads, it seems like web authors also have opinions!
At this point, the proposals have nearly the same functionality (with some caveats that Apple is hoping to address). They accept roughly the same options, and use almost the same layout algorithm.
/* grid masonry */
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: masonry;
/* non-grid masonry */
display: masonry;
masonry-tracks: repeat(3, 1fr);
masonry-direction: column;
I think either one will work just fine. But let’s look at the details!
I was about to hit publish when Geoff Graham from CSS Tricks posted a similar rundown: CSS Masonry & CSS Grid. I recommend checking it out!
This is a common debate, and I don’t find it that useful. I’m not here to protect the pure platonic essence of ‘grid-ness’ being sullied by un-aligned rows. There is extensive overlap between masonry and grid functionality. The terminology can adapt to our needs.
The most I can manage for this line of debate is to say: web author mental models matter. On that front it’s clear that authors (like spec editors) see things very differently from each other. We’re divided.
Ian Kilpatrick (Google) raised some issues early on, suggesting that even the overlapping parts of grid and masonry (defining the columns) must have slightly different algorithms:
It’s clear that there has been a lot of effort since then to bring the two approaches in line with each other. The current proposal works by first placing ‘hypothetical’ items in every position that the item could potentially occupy – and then proceeding with normal grid track sizing, before finally placing the actual items in their actual positions.
There may still be more to work out here, but the goal seems to be making both approaches work the same. That process should help reveal any fatal flaws in either proposal, and ensure our final choice is based only on the fundamental differences.
It’s worth letting that play out some before we make decisions based purely on syntax. Still, there are a lot more considerations to keep in mind.
When we’re working on new features, we hope they will be easy to teach and easy to learn. That’s essential if we want people to use the feature! But it has some of the same issues as the previous debate. Who are we teaching, what do they know already, and how do they think about layout?
The two proposals provide slightly different abstractions.
There are advantages to understanding one unified track-layout system, and giving it additional flexibility. If you know how to use grids, the masonry value becomes a smooth extension of that syntax.
There are also advantages to a customized syntax, specialized for a specific use-case. If you don’t know grids, the alternative masonry layout mode might provide a simpler entry point. In either case, we can (mostly) re-use the track-definition syntax.
Unfortunately, both options result in some properties that look similar – sharing names or syntax – but have subtly different rules in masonry.
Still, I feel like I could learn or teach either one.
To get from display: grid
to a basic masonry layout
requires us to set both
columns
and rows
–
one of them as the masonry
axis,
and the other with our desired tracks.
If we don’t define multiple tracks,
we’ll get a single masonry column by default.
We could consider this a good small-screen default,
and then expect authors to add columns as needed.
But display: masonry
is a specialized layout mode,
so the defaults can reflect a more common
‘masonry’ approach:
adding new tracks as the space becomes available.
In the proposed spec,
masonry-template-tracks
will have an initial value of
repeat(auto-areas, auto)
.
There’s a lot going on in that value.
The repeat()
function is borrowed from grid,
allowing us to take any number of columns
and duplicate them any number of times.
The first value is how many times to repeat,
and the second value describes the tracks to repeat.
Grid provides a few keywords for the repeat-count:
auto-fit
and auto-fill
.
Both create new tracks whenever space becomes available,
but auto-fit
caps the total number of tracks
based on the number of items in our grid
(and some other criteria) –
to avoid generating empty tracks.
The proposed auto-areas
would generate
zero or more tracks based first on
covering any explicit grid areas needed,
and then falling back to auto-fill
(or maybe auto-fit
) behavior.
This might be useful in both masonry and non-masonry grids,
but is a good default for masonry specifically.
Grid doesn’t currently allow auto-repeating tracks
that use an intrinsic size.
Having auto
in the second argument here
is a powerful new feature.
If this is possible to do in non-masonry tracks as well,
we should make it work everywhere.
But again: it only makes sense as the default
in a masonry-specific layout.
Even assuming these values will work in both syntax options, our simplest possible masonry layout looks a bit different now:
/* grid syntax */
display: grid;
grid-template-columns: repeat(auto-areas, auto);
grid-template-rows: masonry;
/* non-grid syntax */
display: masonry;
The non-grid option is clearly simpler – more can be implied. The grid option is not excessive, just explicit. Both make sense, but they represent different approaches moving forward…
CSS doesn’t add new layout modes all the time, but we do on occasion, and this is likely to come up again. We could imagine a new layout system that is grid-like on one axis, but does something else on the other axis. I don’t think this is far-fetched at all. Columns are useful for alignment, even when the page isn’t a strict 2-axis grid. See, for example, a decade of column-only web ‘grid systems’.
Or just consider masonry as a literal combination of grid columns with flexbox rows:
/* name TBD, maybe 'masonry'? */
display: grid-columns-but-flex-rows;
/* or, extending grid to contain other things… */
display: grid;
grid-template-rows: flex;
The grid-integrated syntax maintains existing properties and values.
There is less ‘new’ syntax we need in the language –
just a single keyword opens up entirely new layout opportunities.
With a small syntax addition,
and a small impact on the overall footprint of CSS,
we’ve opened up an entire range of grid-plus possibilities.
Even as we add new grid-plus-*
features down the road,
the overall language remains streamlined.
A new display mode, on the other hand, adds a whole array of new properties to the language for each additional extension – some of them nearly-exact duplicates of existing grid properties. Our new properties can re-use familiar syntax internally, so it’s not a huge burden to re-learn, but it does involve a lot of new property names that could have been avoided.
As a tradeoff, each of those properties is specialized –
providing a new default for the new layout.
If we add more grid-plus-*
layout modes in the future,
the language will grow quickly,
but each layout mode can get specialized defaults
designed for the use-case in question.
Each individual usage is streamlined.
Both of those maintain one form of ‘simplicity’ at the expense of another. One looks better in isolated demos, but both are important for teachability of the language as a unified system.
Of course, we wouldn’t have to make the same decision every time. Other things to consider case-by-case would be…
There are several situations
when it’s useful to switch between layout methods.
One of those is providing a fallback
in browsers that don’t support the new syntax,
and the other is providing an alternative
under different conditions
(often a @container
or @media
query).
Building masonry into grid
provides us with one very clear fallback path.
If we remove the new masonry
keyword,
we get the same basic grid,
with all the cells aligned on both axis.
You can see this fallback option
in Jen Simmons’ demo
embedded above.
It’s not perfect,
there’s too much space,
but it’s pretty decent for a fallback.
Jen provides some flexbox-based alternatives in her demo, and Chris Coyier has a 2020 article documenting other Approaches for a CSS Masonry Layout. Many of them rely on either JavaScript, flexbox, or multi-column layouts.
I find most of those alternatives unconvincing. Some people have suggested that masonry is ‘closer to flexbox than grid’ because of the dense item-packing. It’s an argument that sounds compelling to me on the surface, but after trying various options, I haven’t actually seen a flexbox fallback that would work for me as a basis for enhancement. Maybe that just comes down to personal taste.
If we do want a flexbox or multi-column fallback,
the grid-based proposal requires a bit more work
to get there.
Since the grid
display mode isn’t going to fail entirely,
we need to override it using @supports
:
/* grid fallback… just leave it */
.grid-fallback {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-template-rows: masonry;
}
/* flex fallback, additional definition */
.flex-fallback {
display: flex;
flex-wrap: wrap;
/* maybe other things? */
@supports (grid-template-rows: masonry) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-template-rows: masonry;
}
}
I’m not sure why we would want that fallback, exactly. Flexbox rows are all vertically aligned, just like our default grid rows – so there’s no clear advantage that I see. But I can imagine situations where we want a flexbox variant at some other screen size, to create a different effect?
An alternate masonry
display mode
would fall back to block
display by default.
To get any other fallback (grid, flex, or multi-column)
we would have to specify both layouts completely.
For a grid fallback
with columns that match our masonry layout,
that would mean duplicating the track definitions:
/* grid fallback… duplicate properties */
.grid-fallback {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
display: masonry;
masonry-template-tracks: repeat(auto-fit, minmax(200px, 1fr));
}
We could put those track definitions inside a variable if we want, but it still requires explicit duplication.
Because we can rely on various properties working in only one layout mode, and we get a simple mode toggle, it’s fairly simple to set up any fallback we want. But the trade off is that we don’t have the the most obvious fallback built in from the start.
While I was experimenting with different fallback paths,
I was trying to maintain the spirit of masonry in some way –
tightly packed columns –
with as little effort as possible.
My favorite solution was the default fallback
provided by grid-integrated masonry,
with the addition of rough aspect-ratio attributes when possible –
e.g. is-short
and is-tall
.
All items span multiple rows,
but short items span fewer rows,
and tall items span more rows.
It’s not a perfect solution, but I was surprised how well it works. This is a damn good fallback story in my mind. Of course, effort here is hard to measure. If we know the size of each item, and have access to the markup, then this is pretty straight-forward. But those are big ifs.
On my personal site, image galleries fit this description. Maybe I’ll start using this approach.
It’s also fair to say that fallbacks become less essential over time. Looking further down the road, we might also want to consider…
Frankly, it’s hard to know. But this is one of the questions that the Chrome team keeps bringing up. If we attach masonry to grid now, they can never diverge in the future. Every future addition to grid will need to take masonry into account.
While I can see features that might build on a masonry precedent, I don’t actually have a clear picture of what might conflict with masonry-in-grid down the road. I don’t see obvious issues that are likely to come up – but Chrome is right that it could theoretically become an issue.
How much do we worry about that now? I don’t know.
Join the conversation! There are several threads where developers can leave feedback:
It’s most helpful to talk about your own use-cases, and how you expect to use this feature (if you do). I avoid arguments about:
display:grid
that focus entirely on one axis.I imagine Apple will also post something soon, to clarify and expand on their position. I’m interested to see what they say. I also think Google might release a prototype soon, which would allow us to compare real code.
For my part, I’m not particularly invested in one outcome or the other. I think both proposals are pretty good, and this conversation has already pushed both to be better than they were initially. So I’m rooting for the process!
Ask the questions! Push the language to be better! Have fun out there, building the web.
Added on
In their response posted Oct 21, 2024, Apple developers make several arguments in favor of the grid-integrated syntax. It’s a long article, and covers a lot of points we’ve already discussed, but a few ideas stand out to me.
They provide use-cases to show where the grid fallback is preferable,
but also how a current masonry-like solution (similar to mine)
could be progressively enhanced in much the same way as subgrid
.
That feeds into their argument that,
while defaults are nice,
most layouts will require more detail –
and often that detail is where we get overlap with grids.
So the simplest cases might be a line or two shorter
with a non-grid approach,
but realistic use-cases are likely to duplicate the work.
Taking that even further,
they call into question how useful it is
to have auto
as a repeating value in masonry layouts.
This caught me off guard –
up above I say it sounds useful –
but I think Apple is right to question that.
There’s a fundamental lack of information
if we ask the browser to figure out how many columns fit,
and also how big those columns should be,
without providing more detail about what to prioritize.
Just because the browser can give us something in this case,
doesn’t mean the result will ever be a useful default.
They also provide more specific examples
of another grid-plus-*
layout that authors would find useful:
grid columns, with ‘normal flow’ rows.
That’s a feature that I would be very excited to see.
I don’t know if their proposed syntax will work,
but I agree that it should also
re-use existing properties wherever possible,
rather than being treated as a distinct layout method.
At this point, I’m fairly well convinced that the grid solution is a better path forward – though I’m still not sure what ‘future conflicts’ Google is concerned about.
At the end of the article, there is a suggestion that we re-think the name of this feature. The only real argument for ‘masonry’ is a popular tool with the same name – and a metaphor that’s not particularly precise.
I agree, but don’t love their proposed collapse
or pack
values.
Do you have better ideas?
Let us know on
Mastodon
or Bluesky!
New layouts will be possible
The more I play with it, the more convinced I am that anchor positioning is going to unlock some surprising new layouts.
Performance, scope, and fallbacks for the anchor positioning polyfill
Our sponsors are supporting the continued development of the CSS Anchor Positioning Polyfill. Here’s a summary of the latest updates.
Are we measuring what we meant to measure?
There’s been a lot of interest in the results of the annual State of CSS survey, but are we asking all the right questions?