Josh Lewis

The problem with CSS grids

Grids are a great tool to bring order to a site and speed up development, but stuffing grid-based markup into our sites isn't that much better than the table-based designs of old.

A typical bootstrap-style css grid will have markup that looks like this:

<div class="grid">
    <div class="grid__item large--1-4"></div>
    <div class="grid__item large--3-4"></div>
</div>

The .grid (also often a row class) will have a negative margin allowing for the same padding on every .grid__item. A very common pattern is also to have various prefixed classes based on screen size such as .large--1-4, .medium--1-4, .small--1-4.

I would argue almost every aspect of this is wrong:

  • It is not semantic in any way.
  • The responsive classes are defined at fixed sizes, when I believe they should be dependent on the content.
  • It bloats our HTML and CSS.

The first and last points are made worse by the fact these are padding-based grids. If we want the first element to have a background colour or border for example, we need to nest another element within it so we end up with three levels of nested divs:

<div class="grid">
    <div class="grid__item large--1-4">
         <div class="sidebar"></div>
    </div>
    <div class="grid__item large--3-4">
         <div class="content"></div>
    </div>
</div>

Which really isn't all that different from the markup we had years ago in table based layouts in terms of semantics and readability.

An alternative?

Fortunately CSS is moving forwards rapidly. The upcoming grid layout will likely be the best solution in the future but for now we are limited by browser support to flexbox, which is nearly as good.

With flexbox it is possible to eliminate the third level of HTML markup and with the help of some CSS inheritance via LESS or SCSS produce markup that is far cleaner and more semantic.

The key is to use margins instead of padding as this allows our grid children to be the children and not just wrappers. We can still change the background colour, padding and borders of these elements.

The code is fairly trivial. Apply display: flex to the container and a margin to each child element. The bit I like the least is that because we are using margin we need to use calc() to set the width to be a percentage minus the gutter:

$gutter: 20px;

@mixin grid_size($width) {
    width: $width;
    flex: 0 0 calc(#{$width} - #{$gutter});
}

%grid{
    display: -webkit-flex;
    display: flex;
    margin: 0 $gutter*2;
    > *{
        margin: $gutter;
        width: 100%;
    }
}

Automatic grids

With one semantic class on the parent flexbox will auto-fit the number of columns based on the number of children:

.page{
    @extend %grid;
}
<div class="page">
    <div></div>
    <div></div>
    <div></div>
</div>

Defined widths

Of course in many cases you won't want every column to be the same width automatically. This is all we need to produce basic, semantic, grid layouts:

.page{
    @extend %grid;
}
.sidebar{
    @include grid_size(25%);
}
.content{
    @include grid_size(75%);
}
<div class="page">
    <div class="sidebar"></div>
    <div class="content"></div>
</div>

Another nice thing is that the columns will always be the same height unless you set max-height which is a common pain point when using floated grids:

Long
content
here.
Notice that the sidebar is the same height automatically.

Full width grids

If we don't want the outside margins we can simply extend the grid class and have flexbox take care of the rest:

%grid--full{
    @extend %grid;
    margin: 0 (-$gutter);
}

Auto columns constrained

What if we want to display lots of items in a grid? We don't want them to squish all on to one line so we can again overide the grid class to fix the children elements and allow wrapping:

Here I'm using a SCSS for loop to generate 12 classes %grid--1 to %grid--12.

@for $i from 1 through 12 {
    %grid--#{$i}{
        @extend %grid;
        flex-wrap: wrap;
        > *{
            width: calc((100% / #{$i}) - #{$gutter} * 2);
        }
    }
}

Which can then be used like this:

.products{
    @extend %grid--3;
}
<div class="products">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</div>

Summary

So flexbox allows us to make the HTML less verbose and get rid of the three levels of markup. This is possible with floated bootstrap-style grids and using silent classes or extending classes and with the use of calc(). However, by using flexbox we can let the browser automatically figure out the columns needed based on content.

Using silent classes or at least extending classes makes our css and markup more semantic, like it should be.

For small projects I don't see why this shouldn't be used today, the support is there. But this is of course not a definitive solution, but something interesting and worth playing with.

The full code

All 31 lines of it:

$gutter: 20px;

@mixin grid_size($width) {
    width: $width;
    flex: 0 0 calc(#{$width} - #{$gutter});
}

%grid{
    display: -webkit-flex;
    display: flex;
    margin: 0 $gutter*2;
    > *{
        margin: $gutter;
        width: 100%;
    }
}

%grid--full{
    @extend %grid;
    margin: 0 (-$gutter);
}

@for $i from 1 through 12 {
    %grid--#{$i}{
        @extend %grid;
        flex-wrap: wrap;
        > *{
            width: calc((100% / #{$i}) - #{$gutter} * 2);
        }
    }
}