Using Sass to generate a responsive grid

We should all be aware by now that responsive grids are the in thing, before we can reliably get our hands on technologies like flexbox, creating a responsive grid system is the go to method for (almost) bulletproof responsive layouts.

With this in mind, assuming you’ve used a grid system before, you will have no doubt come across a bunch of HTML classes such as .col, .row, col-2, .col-3, .col-small-10 etc… You might spot a possible issue if you were to use plain vanilla CSS, in that to write these all out and work out the sizes of each column would take a lot of time and tedious work/copying and pasting.

Now what if there was a way that Sass could do all of that for us, whilst also letting us have complete control over how many columns we want in our grid and at what media query breakpoints.

Well great news, it can! Let’s get started.

Firstly let’s make a list of what we want:

  • Support for multiple breakpoints
  • Sized column classes for each breakpoint, not just stacking to single column below a certain breakpoint.
  • Variables for the amount of columns per row, grid width, width of each column and margin between (in percentages)
  • Naming convention of .grid for the container, .row for each row and .col and .breakpoint-columnamount for each column

Great, now we have an idea of what we want, let’s get started.

First let’s set up the breakpoints we intend to use. I’ve gone for 4 at the standard iphone, ipad and desktop sizes.

// Breakpoints
$small: 30.063em; // Above 320px
$medium: 33.813em; // Above 480px
$large: 48em; // Above 767px
$xlarge: 64em; // Above 1024px

Instead of writing out each breakpoint ourselves we’re going to take advantage of the Sass @each loop, to loop over each of our breakpoints and output the relevant CSS. To do this we’ll need an array of each of our breakpoint variables.

$breakpoints: $small $medium $large $xlarge;

We’ll also need an array containing the breakpoint alias or slug for each breakpoint we’re looping over. For our base columns (below 320px) we’ll go with base as our alias.

$breakpoint-alias: base small medium large xlarge;

Now let’s set up the variables for the size of our grid. For this example I’m going to go with a 24 column grid with a maximum width of 1170px. I’m not overly fussed about the size of each column but I want the margin between each to be 30px at its widest. I could have made a fancy calculation for this but for the purposes of this I’ve just worked it out myself. Extra credit for anyone who wants to add that in!

$grid-columns: 24;
$grid-width: 73.125em;
$column-width: 1.709404%;
$column-margin: 2.5641%;

Before we get into our loop lets set up some base Sass for our grid. Everything is fairly straight forward here. You’ll notice that each column has its margin on the right with the last column having no margin (meaning the space between the edge of the container and the columns is controlled by the padding on the row)

.grid {
    float: left;
    clear: both;
    width: 100%;
}
.row {
    max-width: $grid-width;
    margin: 0 auto;
    padding: 0 1.25em;
}
.col {
    float: left;
    width: 100%;
    margin: 0 $column-margin 0 0;
    &:last-child {
        margin-right: 0;
    }
}

Now the exciting bit, let’s get into our loop!

First I want to set up a counter variable to increment each time we’re done outputting CSS for a breakpoint.

$current-breakpoint: 1;

Now let’s set up our @each loop to go over each breakpoint in our array $breakpoints.

@each $breakpoint in $breakpoints {
For the sake of ease, let’s set up a variable to contain the alias of our current breakpoint using Sass’ handy nth() function, where we pass in first the array in which we’re looking and then the value of the record, in this case our counter we just set up: $current-breakpoint
    $slug: nth($breakpoint-alias, $current-breakpoint);
Unfortunately Sass doesn’t have a way for us to conditionally output our media query (that I’m aware of) so we’ll just have to use a plain old if statement and a bit of copy and paste!
    @if $current-breakpoint == 1 {
Now within our @each we want to output CSS for each size column so well use a for loop which goes from 1 to the the $grid-columns variable we set up earlier.

        .col {
            @for $i from 1 through $grid-columns {

Now we’ll build the CSS we want to output for each column class. For this we’ll pass through 2 variables, the slug for the breakpoint and the the counter $i from our for loop.

The CSS we’ll need is (column width x amount of columns) + (size of margins engulfed (bearing in mind that although we have 24 columns in our grid, only 23 of these will have a margin))

                &.#{$slug}-#{$i} {
                    width: ($column-width * $i) + ($column-margin * ($i - 1));
                }
            }
        }
    } @else {

Now, we need the @else statement for our @if where we output the CSS with the required media query.

        @media all and (min-width: $breakpoint) {
            .col {
                @for $i from 1 through $grid-columns {
                    &.#{$slug}-#{$i} {
                        width: ($column-width * $i) + ($column-margin * ($i - 1));
                    }
                }
            }
        }
    }

And don’t forget to update the $current-breakpoint counter for our @each loop.

    $current-breakpoint: $current-breakpoint + 1;
}

And we’re done! Now you can enjoy headache free grid generation with Sass, and if anyone can improve this to make it even simpler I’m all ears.

This grid system is a simplified version of the one I’m working on for personal projects. If you like the look of it you can check out the github repo here, or the full Sass output is below:

// Breakpoints
$small: 30.063em; // Above 320px
$medium: 33.813em; // Above 480px
$large: 48em; // Above 767px
$xlarge: 64em; // Above 1024px

$breakpoints: $small $medium $large $xlarge;
$breakpoint-alias: base small medium large xlarge;

$grid-columns: 24;
$grid-width: 73.125em;
$column-width: 1.709404%;
$column-margin: 2.5641%;
.grid {
    float: left;
    clear: both;
    width: 100%;
}
.row {
    max-width: $grid-width;
    margin: 0 auto;
    padding: 0 1.25em;
}
.col {
    float: left;
    width: 100%;
    margin: 0 $column-margin 0 0;
    &:last-child {
        margin-right: 0;
    }
}

$current-breakpoint: 1;

@each $breakpoint in $breakpoints {
    $slug: nth($breakpoint-alias, $current-breakpoint);
    @if $current-breakpoint == 1 {
        .col {
            @for $i from 1 through $grid-columns {
                &.#{$slug}-#{$i} {
                    width: ($column-width * $i) + ($column-margin * ($i - 1));
                }
            }
        }
    } @else {
        @media all and (min-width: $breakpoint) {
            .col {
                @for $i from 1 through $grid-columns {
                    &.#{$slug}-#{$i} {
                        width: ($column-width * $i) + ($column-margin * ($i - 1));
                    }
                }
            }
        }
    }
    $current-breakpoint: $current-breakpoint + 1;
}