Skip to main
Article
A gallery of numbered images in four columns

Choosing a Masonry Syntax in CSS

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.

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:

See the Pen Masonry Layout Demo by @jensimmons on CodePen.

This demo works on Safari Tech Preview, or Firefox with an experimental feature flag.

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!

Hot off the press:

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.

See the Pen Rough Puff Masonry by @miriamsuzanne on CodePen.

I borrowed the markup and images from Jen Simmons, and I’m pretty happy with the result.

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:

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.

Mia from behind,
standing at a laptop -
speaking to a conference audience
and gesturing to one side

Cascading Style Systems

A workshop on resilient & maintainable CSS

New CSS features are shipping at an unprecedented rate – cascade layers, container queries, the :has() selector, subgrid, nesting, and so much more. It’s a good time to step back and understand how these tools fit together in a declarative system – a resilient cascade of styles.

Register for the October workshop »

Recent Articles

  1. see all Article posts
  2. Article post type

    Partial Feature Queries, Relaxed Layout Containment, and More

    CSS Working Group updates from July

    Over the last month, the CSS Working Group has determined we can loosen containment restrictions for query containers, and agreed on a syntax for special-case support queries (like support for the gap property in a flex context, or support for align-content in a block flow context).

    see all Article posts
  3. A stepped gradient of a pink hue in 2% lightness increments from 100% to 58%, labeled 'spec'
    Article post type

    CSS Working Group Updates for June & July

    What I’ve been working on as an Invited Expert

    The CSS Working Group has regular face-to-face meetings (hybrid online/in-person) throughout the year, and they always result in a flurry of activity! Here’s a rundown of some highlights from the last few months, with a focus on the features I maintain.

    see all Article posts