A Grid System for 2015

A Grid System for 2015

A Grid System for 2015

For many years, the popular methods of layout have involved float or inline-block. A less popular alternative involved using display: table;. All three techniques have caveats and limitations, or involve ‘hacks’ – or workarounds that others consider to be hacks – and this is because these CSS properties weren’t intended to handle robust layouts.

I sought a better alternative to these classic layout methods. As this is 2015, a time where some really incredible things can be done with pure CSS3 – surely an improved grid system should be trivial?

The Old Guard

My personal preference has been inline-block grids for years, but some other developers despise the commented-out white-space, and that can be a deal breaker. There are workarounds, but none of them are ideal: you can hackily remove the white-space with negative word-spacing set on the container, which is liable to breakage across different browsers with different pixel rounding; you can set the font-size on the container to zero and then back to normal within the cells; or you can set the grid container to use a blank font (where spaces have no width), which is a pretty cool technique but does mean sending an additional font request(s) over the wire.

With floats, I found the lack of vertical alignment and the loss of a pseudo-element to a clearfix more frustrating than having to comment out some white-space.

Table grids have some pretty impressive features – you can arbitrarily control vertical alignment and the cells will be of equal height automatically – but the one-container-per-row ruins it for me – it means that multiple breakpoints require grids nested in grids which can quickly get out of hand.

So here’s where we are so far, we need our to new grid system to have the following capabilities:

  • No commented white-space requirement
  • No clearfix requirement
  • Simple markup structure i.e. no requirement to nest grids for multiple breakpoints
  • Control over vertical and horizontal alignment
  • Reversible source order
  • Discrete cell ordering
  • Equal-height cells without JavaScript

The New Tech

We have at our disposal a pair of new CSS technologies that can help us achieve what we need here:

Flexbox

Flexbox can do all of these, and more! Hang on though:

But what about browser support?

This is still a valid concern, but support is getting there. Within GRIT, we support the three latest major versions of the most popular browsers. And at the time of writing, needing to support IE9 means we can’t use flexbox without polyfilling it in some capacity. However, with Microsoft Edge coming out soon, this will no longer be the case.

Beyond IE9 we’re almost there bar the odd inconsistency: IE10 sometimes needs a little attention in order to play ball.

But isn’t Flexbox not really appropriate for this?

Unfortunately that’s true. As this article and others state, Flexbox is more intended towards smaller components and chunks of layout, like a stretchy navbar, for example.

That said, inline-block, float and table were not intended to solve layout problems either, so I think flexbox can’t be written off here. One of my first concerns was performance: Google’s rendering performance documentation states that the newest implementation of flexbox is considerably faster than float; but conversely an article from front-end developer Ben Frain says that flexbox is slower than display: table – but not enough to be concerned about.

Display: Grid

From CSS-Tricks:

The grid property in CSS is the foundation of Grid Layout. It aims at fixing issues with older layout techniques like float and inline-block.

Sounds very promising. However:

But what about Browser support?

The stumbling block for grid comes in the form of support. It’s currently only supported in IE10+. I’ve spent some time experimenting with it and it looks incredibly promising, but we need something we can use in production and grid is not our answer; not yet, and likely not for some time. Watch this space.

Flexbox it is

Flexbox is the one worth exploring at this point, but it’s important to be aware of the above regarding support, performance and general best practice. As more articles and code examples surface this will hopefully become clearer over time.

The Code

SCSS

// Define default grid gutter
$grid-gutter-width: 20px !default;

// Create grid mixins
@mixin grid($gutter-width: $grid-gutter-width;) {
    list-style: none;                       // Enable usage on <ul> and <ol>
    margin: 0 0 0 (0 - $gutter-width);      // Remove margin and set negative gutter-margin
    padding: 0;                             // Remove unwanted padding
    display: flex;                          // Magic
    flex-wrap: wrap;                        // Allow cells to overflow over multiple rows
}

    @mixin grid-item($gutter-width: $grid-gutter-width) {
        width: 100%;                        // Have cells full-width by default
        padding-left: $gutter-width;        // Add the gutters
    }

/**
 * Basic Grid object
 */
.grid {
    @include grid;
}
    .grid-item {
        @include grid-item;
   }

Sizing the Cells

My preference for handling grid sizing is to use multiple classes assigned to breakpoints, but we should also allow the flexibility for clean markup, so let’s create a mixin that generates the CSS we need here:

@mixin grid-width($fraction) {
    @if ($fraction == auto) {
        flex: 1 1 0%;                 // Harness flexbox's automatic sizing if wish
    } @else {
        width: percentage($fraction); // If not, use a fixed percentage width
    }
}

// Usage: @include grid-width(1/2);

Here, I add some width utility classes, nested in breakpoints with the mixin like so:

.u-auto {
    @include grid-width(auto);
}

.u-1/2 {
    @include grid-width(1/2);
}

@media screen and (min-width: 768px) {
    .u-auto-desk {
        @include grid-width(auto);
    }
    .u-1/2-desk {
        @include grid-width(1/2);
    }
}

And so on. That gives us the flexibility to control each cell over a variety of breakpoints, so our HTML ends up as follows:




<div class="grid">



<div class="grid-item u-1/2 u-1/3-desk">
        Half by default, third on desktop-sized devices
    </div>





<div class="grid-item u-1/2 u-auto-desk">
        Half by default, fills up remaining space on desktop-sized devices
    </div>



</div>



With this, the markup can quickly become a mess of classes. Creating clean HTML is easy though:

main {
    @include grid;
}

    article {
        @include grid-item;

        @media screen and (min-width: 768px) {
            @include grid-width(auto);
        }
    }

    aside {
        @include grid-item;

        @media screen and (min-width: 768px) {
            @include grid-width(1/3);
        }
    }
<main>



<article>
        Full-width by default, auto-sized on desktop-sized devices
    </article>





<aside>
        Full-width by default, third on desktop-sized devices
    </aside>



</main>

Alternatively, you could create silent classes and @extend those to keep your code DRY:

%grid {
    @include grid;
}

    %grid-item {
        @include grid-item;
    }

// Etc...

main {
    @extend %grid;
}
    article {
        @extend %grid-item;
        @extend %u-desk-auto;
    }

// Etc...

So we have our basic responsive grid setup, let’s get on to the required features.

The Features

Horizontal Alignment

The align-items property is set to stretch by default which will enable us to create equal-height columns – we’ll come to those shortly.

.grid {
    justify-content: center | flex-end; // center or right alignment respectively
}

You can also arbitrarily align individual cells in the same fashion:

.sidebar {
    @include grid-item;
    align-self: flex-start | center | flex-end;
}

Reverse Source Order

.grid {
    flex-direction: row-reverse;
}

Cell Ordering

Flexbox makes discrete cell ordering very easy, in the following example the .content element will appear after the .sidebar regardless of its position in the source:

.content {
    @include grid-item;
    order: 2
}

.sidebar {
    @include grid-item;
    order: 1;
}

Equal-Height Columns

We touched on the align-items: stretch; property above which will help us with equal-height columns. If we were to apply a background-color to our grid-cells, they would already be of equal height:

.grid-item {
    @include grid-item;
    background-color: #efefef;
}

But there’s a problem – the gutters are generated using padding, which means they too have the background colour.

We can get around this by adding display: flex; to the .grid-item, which will cause its child elements to be stretch to the full height of that cell:




<div class="grid-item">



<div class="box">
        I will fill the available height of this row
    </div>



</div>



This works, but it’s not ideal, we have surplus markup – and the display: flex; on the .grid-item element means that multiple children within the cell will not display as expected.

We’d love to be able to add the .box class to the cell itself, but the problem is the padding. If margin were used for the gutters instead, it would be ideal, but we’ll have to rethink how our grid sizing works. Let’s revisit two of the original mixins:

@mixin grid-item($gutter-width: $grid-gutter-width) {
    width: 100%;
    margin-left: $gutter-width; // Use margin for the gutters
}

@mixin grid-width($fraction, $gutter-width: $grid-gutter-width) {
    @if ($fraction == auto) {
        flex: 1 1 0%;
    } @else {
        width: calc(#{percentage($fraction)} - #{$gutter-width}); // Use a fixed percentage width, minus the width of the gutter
    }
}

Great, this now works as want it to: we don’t have to nest markup and we can add aesthetic classes to the .grid-item elements.

Conclusion

So where are we now? We have a powerful, fully-featured and modern grid system with which to layout our websites. It’s a vast improvement on the tech we were battling with before and shoehorning functionality into, but is it best practice?

The answer isn’t clear and will, at this point, be largely down to personal preference. As mentioned above, we have conflicting articles and information regarding the performance of flexbox, and we also have the younger display: grid; shaping together slowly in the spec.