DEV Community

Cover image for Building a responsive, progressively enhanced, masonry layout with only CSS and HTML
George Griffiths
George Griffiths

Posted on • Originally published at griffa.dev

Building a responsive, progressively enhanced, masonry layout with only CSS and HTML

A few days ago I found that I had talked myself into re-designing a website. The website is for my brothers' guitar teaching business which I had previously worked a few years ago, when I was a student. (if you click that link and view it before the rework, yeah I know the site needs work, that's what i'm doing here).

As part of the redesign I was trying to think of ways to lay out testimonials from students, which may have varying length/content, I stumbled onto the idea of using a masonry layout (think bricks, think Pinterest).

Mock masonry design testimonials that inspired this tutorial

There are many ways to go about building out a Masonry layout, this CSS tricks article lays out a few of the options, Approaches for a CSS Masonry Layout.

When looking into the solution I knew I wanted to adhere to a couple of rules:

  • No committing the huge sin of using JavaScript for layout.
  • It's okay if the experience upgrades or downgrades based on browser support.

What are we building

  • Masonry layout using grid-template-rows: masonry
  • CSS Columns as a fallback

This article will explain how to build out the following responsive masonry cards layout with only HTML and CSS.
This final demo uses some features only available in Firefox, behind a flag, which is detailed in the article.

Here is the full finished demo code:

You'd think CSS Grid could do it

CSS Grid is amazing, and you'd think it would have shipped with a simple way of doing masonry layouts, the initial versions of the spec however, did not ship anything to really help out with these layouts.

But fear not, Firefox has something cooking!

Before I continue, the specification we're going to look into is experimental, and at the time of writing only is available in Firefox behind a flag, however, this article will outline how you could use the feature today and fallback to another approach for browsers without support.

Enabling Masonry layout in Firefox

It's January 2021, i'm using Firefox 84.0.2, depending on how the spec has progressed, you may not need to enable this flag, it may even be available in other browsers, this link may have more information.

To enable in Firefox:

  1. type about:config into your address bar
  2. Accept the warnings
  3. In the search box find: layout.css.grid-template-masonry-value.enabled and enable it.

Over on MDN you can find this article: CSS Grid Layout > Masonry Layout

It details, an example very similar to this:

With this small amount of CSS we can get a layout which is almost what we want already, exciting! It has hardcoded sizing, lets improve it, and enable responsive masonry layouts,

Building out our example

Let's start with a fresh example, starting with a little html and "lorem ipsum" to generate some "bricks" for masonry layout.

To see the effect you will need to be view these examples in a browser that supports masonry layouts (at the time of writing Firefox behind a flag).

This code will result in a masonry style layout, which automatically reduces the number of columns as the screen shrinks down, until you reach a single column.

Animated resizing of demo showing masonry layout with responsive columns with css grid

If you run this example you should notice that there is no clean edge on the bottom, it has a wonky bottom.

Image of result of demo showing masonry layout without a flat bottom

No one likes a soggy wonky bottom.

The masonry specification also allows for a new property: align-tracks, which you can set to stretch to fix this problem.

align-tracks also has some other support modes, beware of potential issues with align-tracks if you want a masonry layout where a "brick" can span multiple columns, hopefully these things will get ironed out as the spec progresses.

.masonry {
      display: grid;
      gap: 1rem;
      grid-template-rows: masonry;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      /* no more wonky bottom */
      align-tracks: stretch;
}

/* some styling not important */
.brick {
    color: var(--Text, #F0F0F0);
    background: var(--Primary, #74baff);
    padding:1rem;
}
Enter fullscreen mode Exit fullscreen mode

And now as if by magic, the bottom edge is now straight.

Image of result of demo showing masonry layout without a straight bottom

What about a fallback

Okay, so now we made something work in a single browser only, and it's not even shipped there, not ideal.
Let's try to achieve something similar, this time with CSS columns.

I'd never used CSS columns before looking into them for implementing this masonry layout, they are actually quite clever, and they can made to be responsive, without media queries, which we will take a look at now.

For this example, i'm going to start to make use of CSS Custom Properties which will become extremely useful once we enable our progressive enhancement, we can use these variables to share sizes and other config.

For the css we're going to make use of some column properties:

    column-gap: 1rem;
    column-fill: initial;
    column-width: 300px;
Enter fullscreen mode Exit fullscreen mode

Setting a column-width will set a minimum width of a column before columns need to shrink down, meaning on smaller devices we can get a single column, the same as with our CSS grid implementation.

If you want to set a specific number of columns you can use the column-count property. You could adjust the number of columns using CSS media queries too, if you wanted to:

Optional example:

/* larger */
@media (min-width: 1024px) {
  .masonry {
    column-count: 4;
  }
}

/* medium */
@media (max-width: 1023px) and (min-width: 768px) {
  .masonry {
    column-count: 3;
  }
}

/* small, anything smaller will be 1 column by default */
@media (max-width: 767px) and (min-width: 540px) {
  .masonry {
    column-count: 2;
  }
}
Enter fullscreen mode Exit fullscreen mode

We're not going to do that, we're just going to make use of column-width and allow our columns to grow up to a maximum card width.

Same html as before, with some changes to the css:

The result is very similar, however, there are two main differences:

  • The order of items goes down the columns as opposed to across the rows with css grid. Columns: ⬇️ Grid: ➡️
  • We have the wonky bottom back (there is no easy fix with css columns)

The first difference may be a deal breaker, depending on the use case, for me, order didn't really matter very much.

Animated resizing of demo showing masonry layout with responsive columns with css columns

Progressive enhancement

Now we've seen how to implement a masonry layout in the future and with a fallback, lets put it all together with the magical @supports keyword in css.

@supports(grid-template-rows: masonry) {
    .masonry {
      display: grid;
      gap: var(--masonry-gap);
      grid-template-rows: masonry;
      grid-template-columns: repeat(auto-fill, minmax(var(--masonry-brick-width), 1fr));
      align-tracks: stretch;
    }

    .masonry > * {
        /* use this to reset the margin that the column variant set */
        margin-bottom: initial;
    }
}
Enter fullscreen mode Exit fullscreen mode

Again, with no changes to the HTML again, we're going to implement an @supports just for the masonry layout using css grid:

.masonry {
    --masonry-gap: 1rem;
    --masonry-brick-width: 300px;
    column-gap: var(--masonry-gap);
    column-fill: initial;
    column-width: var(--masonry-brick-width);
}

.masonry > * {
    break-inside: avoid;
    margin-bottom: var(--masonry-gap);
}

@supports(grid-template-rows: masonry) {
    .masonry {
      display: grid;
      gap: var(--masonry-gap);
      grid-template-rows: masonry;
      grid-template-columns: repeat(auto-fill, minmax(var(--masonry-brick-width), 1fr));
      align-tracks: stretch;
    }

    .masonry > * {
        margin-bottom: initial;
    }
}

/* some styling not important */
.brick {
    color: var(--Text, #F0F0F0);
    background: var(--Primary, #74baff);
    padding:1rem;
}
Enter fullscreen mode Exit fullscreen mode

Now with this example, depending on what your browser supports, you can either get the CSS Grid or the CSS Columns version.

As long as the grid-template-rows: masonry doesn't change between now and more browsers shipping, we have just written some CSS that will automatically upgrade itself over time, fixing the technical debt/UX "bug" of a wonky bottom and vertical ordering instead of horizontal.

Bonus round

In my implementation I was toying around with having different cards backgrounds every nth "brick" in the masonry layout.

.brick:nth-child(4n - 7) {
    background: #5A363A;
}

.brick:nth-child(4n - 6) {
    background: #82212C;
}

.brick:nth-child(4n - 5) {
    background: #3A3E41;
}

.brick:nth-child(4n - 4) {
    background: #292A2B;
}
Enter fullscreen mode Exit fullscreen mode

There are some really neat tricks you can do with the nth-child property in CSS, some useful background reading if you are new to this:

Here's the full demo:

Thanks for reading all the way through, I hope this was useful and one day soonish we will be able to easily deal with wonky bottoms in just CSS.

Top comments (0)