Feedback needed: How should we define CSS masonry?

Ian Kilpatrick
Ian Kilpatrick
Tab Atkins-Bittner
Tab Atkins-Bittner

Published: September 19, 2024

The CSS Working Group has combined the two CSS masonry proposals into one draft specification. The group hopes that this will make it easier to compare the two, and make a final decision. The Chrome team still believes that a separate masonry syntax would be the best way to proceed. While the biggest performance issue mentioned in our previous post is resolved, there are still concerns around syntax, initial values, and how easy a version combined with grid would be to learn.

To test our assumptions however, we've worked through some examples to show how masonry would work with each version. Take a look at the examples in this post and give us your feedback, so we can make a decision and proceed with this feature.

This post doesn't cover all possible use cases, however it's clear that separating masonry from grid layout won't result in the feature lacking functionality. In fact, the opposite may be true. As you'll see in this post, the display: masonry version creates new opportunities and a simpler syntax. In addition, many developers have raised concerns about the potential of re-ordering items with masonry causing accessibility issues. This is also being addressed for both versions of the syntax, through the proposed reading-flow property.

A basic masonry layout

This is the layout most people imagine when thinking about masonry. Items display in rows, and after the first row is placed, subsequent items move into space left by shorter items.

A layout with columns, the items filling columns do so with no gaps.
In this layout columns are defined, then items are filled in by masonry rather than strict rows.

With display: masonry

To create a masonry layout use the value of masonry for the display property. This creates a masonry layout with column tracks that you define (or are defined by the content) and masonry in the other axis. The first item is displayed at block and inline start (therefore top left in English), and items are laid out in the inline direction.

To define tracks use masonry-template-tracks with track listing values as used in CSS grid layout.

.masonry {
  display: masonry;
  masonry-template-tracks: repeat(3, 1fr);
  gap: 10px;
}

With display: grid

To create a masonry layout, first create a grid layout using the value of grid for the display property. Define columns with the grid-template-columns property, then give grid-template-rows a value of masonry.

This will create a layout as you'd expect with auto-placed grid items, however the items in each row use a masonry layout and will rearrange to take up space left by smaller items in the preceding row.

.masonry {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: masonry;
  gap: 10px;
}

Points to consider between the two options

A notable difference between these methods is that with the display: masonry version, you get a masonry layout even if you don't specify any tracks with masonry-template-tracks. Therefore display: masonry might be all you need. This is because the initial value of masonry-template-tracks is repeat(auto-areas, auto). The layout creates as many auto-sized tracks as will fit the container.

Reversed flow with masonry

The specification includes ways to change the direction of the masonry flow. For example, you can change the flow to display from the block-end up.

A layout with columns, the items filling columns do so from the bottom of the layout.
In this layout columns are defined, then items are filled in by masonry starting at the block end.

With display: masonry

Create a masonry layout with display: masonry, then use masonry-direction with a value of column-reverse.

.masonry {
  display: masonry;
  masonry-template-tracks: repeat(3, 1fr);
  masonry-direction: column-reverse;
}

With display: grid

Create a masonry layout with display: grid and grid-template-rows: masonry. Then use the grid-auto-flow property with a new value of row-reverse to cause the items to layout from the block end of the grid container.

.masonry {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: masonry;
  grid-auto-flow: row-reverse;
}

Points to consider between the two options

The display: masonry version feels very similar to how flexbox works. Change the direction that columns flow using the masonry-direction property with a value of column-reverse.

The CSS grid version uses grid-auto-flow. As currently defined grid-auto-flow: row-reverse and grid-auto-flow: column-reverse would give the same effect. This could be confusing, as you might expect them to do something different.

Masonry for rows

You could also change direction to define rows.

A layout with rows, the items filling rows do so with no gaps.
In this layout rows are defined, then items are filled in by masonry rather than strict columns.

With display: masonry

Create a masonry layout with display: masonry, then set the value of masonry-direction to row. Unless you want your rows to have a specific block size, you don't need to specify any track sizing as the default is auto, therefore the tracks will size to the content they contain.

.masonry {
  display: masonry;
  masonry-direction: row;
}

With display: grid

.masonry {
  display: grid;
  grid-template-columns: masonry;
  grid-template-rows: repeat(3, 1fr);
}

Points to consider between the two options

As with reversed flow, changing the display: masonry version from columns to rows, means changing the value of masonry-direction. With the grid version, you will need to switch the values of the grid-template-columns and grid-template-rows properties. Or, if using the shorthand, change the order of the syntax.

With both of these examples of switching flow, the display: masonry version feels more intuitive. There's a single property controlling flow masonry-direction, it takes one of the following values:

  • row
  • column
  • row-reverse
  • column-reverse

You then add any sizing information needed to masonry-template-tracks, assuming the default auto value is not what you need.

With grid, to do the reverse direction you need to use the grid-auto-flow property, and to do the row masonry switch the value of the grid-template-* properties. It's also not possible in current grid syntax to leave the value for the grid axis undefined. You always need to specify grid-template-* properties on the axis that doesn't have a value of masonry.

Position items

In both versions you can explicitly position items using the line-based placement you will be familiar with from grid layout. In both versions you can only position items in the grid axis, this is the axis with the predefined tracks, you can't position items on the axis that is doing the masonry layout.

With display: masonry

The following CSS defines a masonry layout with four columns. The grid axis is therefore columns. The item with a class of a is placed from the first column line to the third column line, spanning two tracks with the new masonry-track-* properties. This can also be defined as a shorthand of masonry-track: 1 / 3;.

.masonry {
  display: masonry;
  masonry-template-tracks: repeat(4, 1fr);
}

.a {
  masonry-track-start: 1;
  masonry-track-end: 3;
}

With display: grid

The following CSS defines a masonry layout with four columns. The grid axis is therefore columns. The item with a class of a is placed from the first column line to the third column line, spanning two tracks with the grid-column-* properties. This can also be defined as a shorthand of grid-column: 1 / 3;.

If the grid axis is columns then the grid-row-* properties will be ignored, and if the grid axis is rows, the grid-columns-* properties will be ignored.

.masonry {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: masonry;
}

.a {
  grid-column-start: 1;
  grid-column-end: 3;
}

You can use named lines with both syntaxes. The following examples show a grid with two column lines named a.

With display: masonry

The named lines are defined in the track listing value of masonry-template-tracks. The item can be placed after any line named a.

.masonry {
  display: masonry;
  masonry-template-tracks: 100px [a] auto [a] auto 100px;
}

.item {
  masonry-track: a;
}

With display: grid

The named lines are defined in the track listing value of grid-template-columns. The item is placed after the first line named a. If the grid-row property is defined it will be ignored.

.masonry {
  display: grid;
  grid-template-columns: 100px [a] auto [a] auto 100px;
  grid-template-rows: masonry;
}

.item {
  grid-column: a;
  grid-row: a; /* ignored */
}

You can also use named areas in both syntaxes. The following examples show a grid with three tracks named "a", "b", and "c".

With display: masonry

The tracks are named as the value of masonry-template-areas. Because no track sizes are defined, all three default to auto size. The item is placed in the "a" track.

.masonry {
  display: masonry;
  masonry-template-areas: "a b c";
}

.item {
  masonry-track: a;
}

This works the same whether you are defining rows or columns; the only difference would be the masonry-direction property.

With display: grid

For columns, the syntax is essentially identical. Similarly, because no track sizes are defined, all three default to auto size, but you do still need to explicitly say that the other axis is masonry:

.masonry {
  display: grid;
  grid-template-areas: "a b c";
  grid-template-rows: masonry;
}

.item {
  grid-column: a;
}

For rows, however, the value has to be written differently, because grid-template-areas is actually defining a two-dimensional area, and each row is written as a separate string:

.masonry {
  display: grid;
  grid-template-areas: "a" "b" "c"; /* Note the difference, each row is quoted. */
  grid-template-columns: masonry;
}

.item {
  grid-row: a;
}

Points to consider between the two options

With any positioning, the display: masonry syntax works better when it comes to switching direction. As the masonry-track-* property works in whichever direction is the grid axis, all you need to do to change direction is change the value of masonry-direction. With the grid version, at the least you'll need redundant properties to enable the switching. However, see the previous examples for other ways in which changing direction is more complicated with the grid version.

Shorthands

In this post the longhands have been used to make it clearer which properties are in use, however both the display: masonry and display: grid versions can be defined using shorthands.

With display: masonry

The display: masonry shorthand uses the masonry keyword. To create the basic masonry layout use the following CSS:

.masonry {
  display: masonry;
  masonry: repeat(3, 1fr);
}

To create a simple row-based masonry layout:

.masonry {
  display: masonry;
  masonry: row;
}

To define tracks and a row-based layout with the shorthand:

.masonry {
  display: masonry;
  masonry: repeat(3, 1fr) row;
}

With display: grid

To create the basic masonry layout using the grid shorthand.

.masonry {
  display: grid;
  grid: masonry / repeat(3, 1fr);
}

To create a simple row-based masonry layout:

.masonry {
  display: grid;
  grid: repeat(3, auto) / masonry;
}

In more complex examples, because the overall display:masonry syntax is simpler, more properties can be packed together into the shorthand without it becoming overly complex.

For example, imagine creating three columns, named "a", "b", and "c", filled from the bottom up.

With display:masonry

In display: masonry, all three of these can be set together in the shorthand:

.masonry {
  display: masonry;
  masonry: column-reverse "a b c";
}

Because they're auto-sized, you don't need to specify the sizes, but if you wanted a specific size instead, that could be added. For example: masonry: column-reverse 50px 100px 200px "a b c";.

Also, each component can be freely reordered; there is no specific order you need to remember. And if you wanted to do rows instead, all you need to do is swap column-reverse with row or row-reverse; the rest of the syntax stays the same.

With display: grid

In display: grid, these three aspects have to be set separately:

.masonry {
  display: grid;
  grid-template-rows: masonry;
  grid-template-areas: "a b c";
  grid-auto-flow: wrap-reverse;
}

Like the masonry example, this makes all the columns auto size, but if you wanted to supply explicit sizes, you can do so:

.masonry {
  display: grid;
  grid: masonry / 50px 100px 200px;
  grid-template-areas: "a b c";
  grid-auto-flow: wrap-reverse;
}

Or if you wanted to use 'grid' to set sizes and area names all together:

.masonry {
  display: grid;
  grid: "a b c" masonry / 50px 100px 200px;
  grid-auto-flow: wrap-reverse;
}

In both of these examples, the order is strictly required, and different if you wanted rows instead. For example, changing to rows looks like:

.masonry {
  display: grid;
  grid: 50px 100px 200px / masonry;
  grid-template-areas: "a" "b" "c";
}

Or, to put them all into one shorthand:

.masonry {
  display: grid;
  grid: "a" 50px "b" 100px "c"  200px / masonry;
}

Points to consider between the two options

The display: masonry shorthand is likely to be widely used, given that it's a relatively straightforward shorthand. In many cases, for a "standard" masonry layout, you'll just set the track definitions; all other values can assume the default.

The display: grid version uses the existing grid shorthand, which is a fairly complex shorthand, and is in our experience less frequently used by developers. As shown in the preceding examples, even when used for simple masonry layouts it requires care when setting the order of values.

Other considerations

This post looks at some common use cases today, however we don't know what the future might hold for grid or masonry. A big argument for using the separate display: masonry syntax is it allows the two to diverge in future. In particular with initial values—such as those for masonry-template-tracks—it might be useful to do something different in masonry than grid does. We can't change the grid defaults if we go with the display: grid version, this could limit things we might want to do in the future.

In these examples, you can see places where the browser has to ignore properties that are valid in grid if using masonry. For example grid-template-areas, where most of the values get through away as it defines a two dimensional grid layout, in masonry we are only fully defining one direction.

Provide your feedback

Take a look at these examples, and also the draft specification which lays each version out alongside the other. Let us know what you think by commenting on Issue 9041 or, if you prefer to write a post on your own blog or on social media, be sure to let us know on X or LinkedIn.