CSS masonry with flexbox, :nth-child(), and order

On the surface it seems fairly easy to create a masonry layout with flexbox; all you need to do is set flex-flow to column wrap and voilà, you have a masonry layout. Sort of. The problem with this approach is that it produces a grid with a seemingly shuffled and obscure order. Items will be (unbeknownst to the user) rendered from top to bottom and someone parsing the grid from left to right will read the boxes in a somewhat arbitrary order, for example 1, 3, 6, 2, 4, 7, 8, 5, and so on so forth.

Flexbox has no easy way of rendering items with a column layout while using a row order, but we can build a masonry layout with CSS only—no JavaScript needed—by using :nth-child() and the order property. In a gist, here’s the trick to create a row order while using flex-direction: column, given that you’re rendering three columns:

/* Render items as columns */
.container {
  display: flex;
  flex-flow: column wrap;
}

/* Re-order items into rows */
.item:nth-child(3n+1) { order: 1; }
.item:nth-child(3n+2) { order: 2; }
.item:nth-child(3n)   { order: 3; }

/* Force new columns */
.container::before,
.container::after {
  content: "";
  flex-basis: 100%;
  width: 0;
  order: 2;
}

This will create a masonry layout with items rendered as columns but ordered as rows (the gray vertical lines represent the pseudo elements that force line breaks:

1
2
3
4
5
6
7
8
9
10

Let’s break down the problem (or jump to the solution, or see the codepen collection).

Pick your poison: a shuffled order, or weird gaps

Flexbox is not really built for masonry—if you set a fixed height on a flex container (so that items can wrap when they overflow) and flex-flow to column wrap, you’ll achieve something like this:

1
2
3
4
5
6
7
8
9
10

Items are rendered in columns from top to bottom, producing an arbitrary order when read from left to right. This is of course the expected outcome and desirable in many scenarios, but not when we’re trying to create a masonry layout, and it becomes increasingly disorienting as a page grows taller.

If we instead change the flex-direction to row and have elements of varying heights we’ll achieve the correct order but with weird and unexpected gaps all over our grid:

1
2
3
4
5
6
7
8
9
10

So it seems impossible to get the best of both worlds: items rendered as columns but ordered as rows. You might decide to use flex-direction: column and just move around the elements in your HTML to achieve the right visual order, but this can be cumbersome, it’s unnecessarily complex, and it will mess up the tab order of the elements.

Re-ordering elements with order and nth-child()

The order property affects the order of items contained in a CSS flexbox or grid, and we can use it to re-order items for our soon-to-be masonry layout. The order property is pretty straight-forward to use: if you have two elements and one has order: 1 and the other one has order: 2 the element with order: 1 will be rendered before the other element, independent of their HTML source code order.

The CSS masonry solution depends on a detail of the order specification: what happens if two or more elements have the same order value? Which comes first? Flexbox falls back on the source code order: the element that appears first in the source code will be rendered before other elements with the same order value. This fact gives us the possibility to easily re-group items in our grid so that we can change the ordering from columns to rows, while still rendering those rows as columns, using nth-child().

Look at the table below. To achieve a sensible order using flex-direction: row we’d just have to render elements in the default order: 1, 2, 3, 4, 5, 6 , etc.

  Column 1 Column 2 Column 3
Row 1 1 2 3
Row 2 4 5 6
Row 3 7 8 9
Row 4 10 11 12

If we want to achieve the same order while using flex-direction: column we need to change the order of the elements to match the order of each column in the table (rather than each row):

  Column 1 Column 2 Column 3
Row 1 1 2 3
Row 2 4 5 6
Row 3 7 8 9
Row 4 10 11 12

I.e. the first elements in our flexbox layout have to be 1, 4, 7, 10. These items will fill up the first column, followed by 2, 5, 8, 11 for the 2nd column and 3, 6, 9, 12 for the 3rd and last column. This is where the nth-child() selector comes in. We can use it to select every third element (3n), starting with the first element (3n+1), and set all those elements to have the same order value:

.item:nth-child(3n+1) { order: 1; }

This selector sets order: 1 for element 1, 4, 7, 10 in our container, i.e. the entire first column. In other words we’re using a combination of nth-child() and order to re-order items depending on their original order. To create the 2nd and 3rd column we just change the offset:

.item:nth-child(3n+1) { order: 1; }
.item:nth-child(3n+2) { order: 2; }
.item:nth-child(3n)   { order: 3; }

Here we’re producing three sets: 1, 4, 7, 10 (3n+1) with order: 1, 2, 5, 8, 11 (3n+2) with order: 2, and 3, 6, 9, 12 (3n) with order: 3. All together the order becomes 1, 4, 7, 10, 2, 5, 8, 11, 3, 6, 9, 12.

If we make sure to render each of those groups as one column (and no more), it’ll create the illusion that the items have returned to their original order when you read from left to right. If we visually parse the grid as rows the first row will contain the first element from every group (1, 2, 3), the second row will contain the second element from every group (4, 5, 6), and so on so forth. With this technique we can create a masonry layout with items rendered as columns but ordered as rows.

1
2
3
4
5
6
7
8
9
10

How is the tab order affected by shuffling around elements like this? Luckily, not at all. order only changes the visual representation of objects, not the tab order, so tabbing through the grid will work as intended.

Preventing columns from merging

If you have many items in a masonry layout this technique will fairly certainly break down at some point. We’re counting on that every “group” that we’ve created will be rendered as exactly one column but in reality items can have different heights and columns can easily start to merge. The first column could be longer than the other two, for example, which could make the third column start at the end of the second column:

1
2
3
4
5
6
7
8
9
10

The highlighted box here (3) has to be rendered at the start of the third column or the ordering algorithm will break, but if there’s space for another element at the end of the 2nd column it will naturally be rendered there.

We can fix this wrapping issue by forcing columns to restart at certain points. There’s no easy way of saying “this element should line break” with flexbox, but we can achieve this effect by adding invisible elements that take up 100% of the container’s height. As they require 100% of the parent’s height to be rendered they won’t fit in a column together with any other element, so they’ll essentially force line breaks by creating collapsed columns.

We have to insert these line break elements into our grid and array of elements, so what we’re looking for is to create this sequence of elements: 1, 4, 7, 10, <break>, 2, 5, 8, 11, <break>, 3, 6, 9, 12. We can use pseudo-elements on the container to add these, and we can set the order to 2 on both of them. Adding a pseudo-element with :before will make it the first child of the container and adding a pseudo-element with :after will make it the last child of the container, so if we set order: 2 on both of them they will become the first and the last element of the order: 2 “group” (as they appear before and after the other elements): :before, 2, 5, 8, 11, :after.

/* Force new columns */
.container::before,
.container::after {
  content: "";
  flex-basis: 100%;
  width: 0;
  order: 2;
}

I’ve highlighted the pseudo-elements below to show their effect. Notice that despite that box 3 would fit in the 2nd column it’s rendered as the first element in the last column:

1
2
3
4
5
6
7
8
9
10

The solution

As a final step, you need to make sure that your flex container has a set height that makes it taller than your tallest column (so that all columns will fit). Put together, this will produce a CSS masonry layout with three columns (also available as a codepen):

.container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Your container needs a fixed height, and it 
   * needs to be taller than your tallest column. */
  height: 600px; 
}

.item {
  width: 32%;
  margin-bottom: 2%; /* Optional */
}

/* Re-order items into 3 rows */
.item:nth-child(3n+1) { order: 1; }
.item:nth-child(3n+2) { order: 2; }
.item:nth-child(3n)   { order: 3; }

/* Force new columns */
.container::before,
.container::after {
  content: "";
  flex-basis: 100%;
  width: 0;
  order: 2;
}

Your HTML should look like this, with one <div /> for every item in your grid:

<div class="container">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  ...
</div>

Working with more than three columns

To create a masonry layout with more than three columns we need to do a few things: adapt our sorting algorithm, update the width of the items, and insert line break elements manually (instead of using pseudo-elements). For quick access to the final result I’ve compiled a list of codepens showcasing flexbox masonry with 3, 4, 5 and 6 columns.

Since we’re limited to creating just two pseudo-elements with :before and :after we need to resort to manually adding break elements into our container (you need one less break element than the number of columns in your layout). You can just append them to the end of your container, they’ll be sorted into their respective columns:

<div class="container">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  ...
  <span class="item break"></span>
  <span class="item break"></span>
  <span class="item break"></span>
</div>

We’re inserting column breaks as spans in order to sort them independently from the content items. We need a way to “restart” the counting once we reach the break elements, or an uneven number of content items could make the first break element start after the 3rd column, for example. The :nth-of-type selector targets elements of different types independently, so we can decouple the order of the content items and the column breaks like so:

.item:nth-of-type(4n+1) { order: 1; }
.item:nth-of-type(4n+2) { order: 2; }
.item:nth-of-type(4n+3) { order: 3; }
.item:nth-of-type(4n)   { order: 4; }

The break elements, like previously, take up the full height of the container:

/* Force new columns */
.break {
  flex-basis: 100%;
  width: 0;
  margin: 0;
}

This will create a masonry layout with four columns (view codepen):

1
2
3
4
5
6
7
8
9
10
11
12

Here’s the full solution for a CSS masonry layout with four columns:

.container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Your container needs a fixed height, and it 
   * needs to be taller than your tallest column. */
  height: 600px; 
}

.item {
  width:24%;
  margin-bottom: 2%; /* Optional */
}

/* Re-order items into 4 rows */
.item:nth-of-type(4n+1) { order: 1; }
.item:nth-of-type(4n+2) { order: 2; }
.item:nth-of-type(4n+3) { order: 3; }
.item:nth-of-type(4n)   { order: 4; }

/* Force new columns */
.break {
  flex-basis: 100%;
  width: 0;
  margin: 0;
}

This CSS-only way of creating a masonry (or mosaic) layout is surely not as robust, flexible, and foolproof as a JavaScript implementation (like Masonry) but if you don’t want to rely on a third-party library just to create a masonry layout I hope that these layouts tricks can come in handy.

For help with more common CSS flexbox patterns, I’ve compiled a list of flexbox examples that you can copy and paste into your projects and written in-depth about the line-breaking flexbox item technique.