May 04, 2018

Mosaic Layouts with CSS Grid

A couple years ago, the task of converting a design like this into a responsive page would have taken up most of the work day:

A Mosaic Layout

First, the grid: floats, calc(), and negative margin hell. Then, images as backgrounds, because that was the only way to really control its variable size and placement.

I got it done a lot of times, and every single one of them it felt like a bunch of hacks held together by duct tape and wooden sticks.

But hey, it’s 2018! We have grid and object-fit. Let’s see what we can do with that.

The HTML

Let’s start with simple, layout-independent markup:

<div class="container">
    <div class="tile">
        <img src="..." alt="">
    </div>
    <div class="tile">
        <img src="..." alt="">
    </div>
    <div class="tile">
        <img src="..." alt="">
    </div>
    <div class="tile">
        <img src="..." alt="">
    </div>
    <div class="tile">
        <img src="..." alt="">
    </div>
</div>

I’ve omitted the url for the images for clarity, but I’m using images from placeholder.com with this url format: http://via.placeholder.com/800x600/C72B41/800834.

Defining the grid

Before we start typing CSS, let’s take a look at the layout and divide it in equal-sized columns that translate into CSS Grid. Rows don’t need to be of equal size, because we’re just going to let those be calculated by the browser.

A Mosaic Layout

I see six columns and three rows, with each tiles spanning over a number of columns and rows.

Let’s put those six columns in our container, with a grid-gap (or gutter) of 1rem:

.container{
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-gap: 1rem;
}

The value repeat(6, 1fr) is an abbreviation of 1fr 1fr 1fr 1fr 1fr 1fr. We’re telling the container to divide all the available space in six equal-sized columns, with a gap of 1rem between them.

Note how we’re not defining rows. If we don’t define them, the browser will calculate them for us.

Now, because of the size of the images, our page looks this way:

A Mosaic Layout

Let’s fix that:

Sizing the images with object-fit

.tile img{
    width: 100%;
    height: 100%;
    object-fit: cover;
}

What’s going on here? We set the images to 100% width and height, with a setting of object-fit: cover, so they stretch up or shrink down to fit their parent without distortion.

This is how things look in the browser after sizing the images correctly:

A Mosaic Layout

There’s a gap at the right because we have six columns, and only five tiles. Now to the fun part, let’s size those tiles!

Sizing the tiles with column and row span.

Let’s look at this image again, and take a note of how many columns and rows take each tile:

A Mosaic Layout

  • Tile 1 takes 4 columns and 2 rows.
  • Tiles 2 and 3 take 2 columns each.
  • Tiles 4 and 5 take 3 columns each.

Now we just turn that into css:

.tile:nth-child(1){
    grid-column: span 4;
    grid-row: span 2;
}

.tile:nth-child(2),
.tile:nth-child(3){
    grid-column: span 2;
}

.tile:nth-child(4),
.tile:nth-child(5){
    grid-column: span 3;
}

After that, our layout is pretty much done:

A Mosaic Layout

One more thing

Our mosaic layout is responsive, but it’s really unusable at small browser widths, so let’s add a media query to turn the layout into a single column when the browser window hits below 650 pixels wide:

@media screen and (max-width: 650px){
    .container{
        display: block;
    }
    .tile{
        margin-bottom: 1rem;
    }
}

Here we’re just removing the grid capabilities of .container, and giving tiles a margin bottom so they separate nicely from each other.

A Mosaic Layout

Finishing up

In a real-wold scenario, these tiles would contain much more than an image. From this point, you could go on and add that content inside .tile, and design everything independently of the grid layout of the parent.

The main takeaway here is that you can do complex layouts like this one without spending all your energy doing weird calculations and curbing float issues. At the end, the core CSS for this layout took only 30 lines of code. A couple years ago I wouldn’t even dream of that.

Here’s the code in a pen so you can play with it.

If you have anything to say, hit me up on Twitter. Happy coding!

↜ Previous Fixing unresponsive volume keys in a Mac

Next ↝ The solar system dimensions and my ignorance