Categories
css css-grid flexbox html

CSS-only masonry layout

207

I need to implement a masonry layout. However, for a number of reasons I don’t want to use JavaScript to do it.

A grid of multiple columns of rectangles of varying height.

Parameters:

  • All elements have the same width
  • Elements have a height that cannot be calculated server side (an image plus various amounts of text)
  • I can live with a fixed number of columns if I have to

there is a trivial solution to this that works in modern browsers, the column-count property.

The problem with that solution is that elements are ordered in columns:

Starting from the top leftmost box, they're numbered 1 through 4 straight down, the topmost box in the next column is 5, and so on.

While I need the elements to be ordered in rows, at least approximately:

Starting from the top leftmost box, they're numbered 1 through 6 straight across, but because box 5 is the shortest the box underneath it is 7 as it has the appearance of being on a row higher than the next box on the far left.

Approaches I’ve tried that don’t work:

Now I could change the server side rendering and reorder the items dividing the number of items by the number of columns, but that’s complicated, error-prone (based on how browsers decide to split the item list into columns), so I’d like to avoid it if possible.

Is there some flexbox magic that makes this possible?

4

  • 8

    Can’t think of a way that does not depend on predefined heights. If you reconsider JS, have a look at stackoverflow.com/questions/13518653/… where i implement such a solution that is quite simple.

    Jun 5, 2017 at 22:18


  • 4

    I realize that you said CSS-only. I just want to mention that Masonry no longer requires jQuery – the minified library is under 8kb – and can be initialized with html alone. Just for reference jsfiddle.net/wp7kuk1t

    Jul 23, 2017 at 16:59


  • 1

    If you can determine the height of the elements ahead of time, by knowing the line-height, font-size (you’d have to serve a specific font and do some clever calculations), image height, verticle margin and padding, you can do this. Otherwise, you cannot do this using only CSS. You could also use something like PhantomJS to pre-render each element and get the height of that element, but there would be significant overhead/latency added.

    Jul 25, 2017 at 7:28

  • Almost all possible masonry layouts can be found here. Note that there are also js solutions.

    – Flimtix

    Mar 31 at 8:28

23

This is recently discovered technique involving flexbox: https://tobiasahlin.com/blog/masonry-with-css/.

The article makes sense to me, but I haven’t tried to use it, so I don’t know if there are any caveats, other than mentioned in Michael’s answer.

Here’s a sample from the article, making use of the order property, combined with :nth-child.

Stack snippet

.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: 960px;
  
  /* Optional */
  background-color: #f7f7f7;
  border-radius: 3px;
  padding: 20px;
  width: 60%;
  margin: 40px auto;
  counter-reset: items;
}

.item {
  width: 24%;
  /* Optional */
  position: relative;
  margin-bottom: 2%;
  border-radius: 3px;
  background-color: #a1cbfa;
  border: 1px solid #4290e2;
  box-shadow: 0 2px 2px rgba(0,90,250,0.05),
    0 4px 4px rgba(0,90,250,0.05),
    0 8px 8px rgba(0,90,250,0.05),
    0 16px 16px rgba(0,90,250,0.05);
  color: #fff;
  padding: 15px;
  box-sizing: border-box;
}

 /* Just to print out numbers */
div.item::before {
  counter-increment: items;
  content: counter(items);
}

/* Re-order items into 3 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;
  border: 1px solid #ddd;
  margin: 0;
  content: "";
  padding: 0;
}

body { font-family: sans-serif; }
h3 { text-align: center; }
<div class="container">
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  
  <span class="item break"></span>
  <span class="item break"></span>
  <span class="item break"></span>
</div>

7

  • 1

    First off, link only answers is bad ones, as when such link dies, so does the answer. Second, if you read the given answer more carefully, you’ll find what’s mentioned in your link is covered. Simply put, there are too many limitations using Flexbox alone, unless what is mentioned above is not an issue.

    – Asons

    Jun 15, 2019 at 17:45

  • 4

    I have read the accepted answer very thoroughly—you’ll even see some comments I added to the bottom of it. The flexbox approach I linked to is unique and not covered by that answer. I don’t want to repeat the article, because it’s very complicated, and the article does a great job of covering that.

    Jun 15, 2019 at 18:22


  • 1

    The only thing that the linked does different is making use of the order property, making it look like items flows from left to right. Still, its main trick is flex-flow: column wrap, which is mentioned in the above answer. In addition, it can’t flow the items in the way a real masonry does, e.g. if the 3rd and 4th item would fit in the 3rd column, they would, the linked one doesn’t. But again as I said, it all depends on what the requirements are, and in this case (this question), it won’t work as what asked for.

    – Asons

    Jun 15, 2019 at 18:39


  • 1

    Furthermore, it is not about “you don’t want to repeat the article”, an answer should contain the essential part of the code, so it will still be valid when the linked resource dies.

    – Asons

    Jun 15, 2019 at 18:41


  • 1

    Fair points. I thought it would be useful to share this article here anyway, since it builds upon the other techniques mentioned previously, and whilst it doesn’t answer the original question, it might help others for whom these limitations are acceptable. I’m happy to delete my answer and re-share the link as a comment, noting the key difference (order).

    Jun 15, 2019 at 18:48

5

Finally a CSS-only solution to easily create masonry layout but we need to be patient since there is no support for it for now.

This feature was introduced in the CSS Grid Layout Module Level 3

This module introduces masonry layout as an additional layout mode for CSS Grid containers.

Then

Masonry layout is supported for grid containers by specifying the value masonry for one of its axes. This axis is called the masonry axis, and the other axis is called the grid axis.

A basic example would be:

.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-template-rows: masonry; /* this will do the magic */
  grid-gap: 10px;
}

img {
  width: 100%;
}
<div class="container">
  <img src="https://picsum.photos/id/1/200/300">
  <img src="https://picsum.photos/id/17/200/400">
  <img src="https://picsum.photos/id/18/200/100">
  <img src="https://picsum.photos/id/107/200/200">
  <img src="https://picsum.photos/id/1069/200/600">
  <img src="https://picsum.photos/id/12/200/200">
  <img src="https://picsum.photos/id/130/200/100">
  <img src="https://picsum.photos/id/203/200/100">
  <img src="https://picsum.photos/id/109/200/200">
  <img src="https://picsum.photos/id/11/200/100">
</div>

This will produce the following result on Firefox if you enable the feature like explained here: https://caniuse.com/?search=masonry

  1. open Firefox and write about:config in the url bar
  2. do a search using masonry
  3. you will get one flag, make it true

CSS masonry layout

If we reduce the screen screen, the reponsive part is perfect!

Masonry layout using CSS only