CSS Grid Systems with SCSS

We’re going to create our very own CSS grid system. Most developers these days have grown accustom to using a grid system such as Bootstrap, but not many know how they work. It’s actully quite simple,and can be done with just a handful of lines of SCSS. No need to pull in something as monstrous as Bootstrap into your project.

Im going to be using SCSS because it’s what im most familiar with, but this will work with any CSS preprocessor. If you’re not using any preprocessor, now would be a great time to start. Preprocessors save a ton of time especially when it comes to responsive design.

Below is a demonstration of the grid in action. The full code can be seen on my CodePen. At http://codepen.io/MpWassler/pen/GJqrXv

See the Pen SCSS Responsive Grid by mitchel p Wassler (@MpWassler) on CodePen.

Easier Media Queries

To start we’re first going to define a SCSS mixin to make working with media querys easier. I originally got this mix in from Dan Cederholm’s book SASS for Web Designers . That book is what introduced me to SCSS in the first place. Its a great read for anyone interested in CSS preprocessors.



@mixin breakpoint($point) {

    @if $point == tablet {
    @media (min-width: 550px) { @content; }
  }


  @else if $point == large {
    @media (min-width: 1170px) { @content; }
  }
 

  @else if $point == small {
    @media (max-width: 549px)  { @content; }
  }
 
}


The mix-in takes a string, either tablet, large, or small. I try not to use the small version for the sake of being mobile first, but we’ll need it for one particular case later. The mix-in then writes the appropriate media queries for that screen size. This is just a short hand, you can use any breakpoint sizes you like. I change these all the time, depending on the project. I typically use this mixin like so



.some-element{
     height:auto;
     color:#fff;
     @include breakpoint(tablet){
         height:100px;
     };
     @include breakpoint(large){
         height:200px;
     };

This way the mobile styles are the default and the styles for larger screens follow bellow.

Configure the Grid

Next we’ll define the variables for our grid. I base these on a fluid version of the 960 grid system. It is a 12 column grid with a 940px total width (not counting gutters on the ends). With 60px wide columns and 20px wide gutters. We convert those values to percentages to make them fluid. Converting pixels to percentages is simple math. You divide the width by its container’s width, so 60px/940px gives us 0.06382978723404. Then we just move over the decemal place two places and we have 6.38297872340426%. Doing the same for the gutter gives us the following.


$max-width: 940px;
$column-width:6.38297872340426%;
$gutter-width:2.12765957446809%;
$maximum-columns:12;

 

Determine Column Widths

Now that we have our variables, we can write functions to calculate the width of our elements based on those variables. I originally read about these functions in an article in Net Magazine a few years ago. I have been using them ever since


@function columns($columns, $container-columns: $maximum-columns) {
  $width: $columns * $column-width + ($columns - 1) * $gutter-width;
  $container-width: $container-columns * $column-width + ($container-columns - 1) * $gutter-width;
  @return percentage($width / $container-width);
}

@function gutter($container-columns: $maximum-columns, $gutter: $gutter-width) {
  $container-width: $container-columns * $column-width + ($container-columns - 1) * $gutter-width;
  @return percentage($gutter / $container-width);
}

The columns function takes the number of columns for the element to fill, and The number of columns the contain fills (for nesting columns); or if no second value is passed, it uses the maximum number of columns. The gutter function will be used to account for the gutter space between each column. We weill abstract this into a mix-in so it will never need to be called directly. We cant now use or columns function within out SCSS like so:



.some-item{
  width: columns(4);
}

That will set the width to the total width of 4 combined columns and there gutters.

Create a Row Helper

Next we will make a mix in to allow us to define rows so we don’t have to float out elements ourselves. First we will make sure we clear our floats using a basic clearfix hack.


@mixin clearfix { 
  zoom: 1;
  &:before, &:after { content: ""; display: table; }
  &:after { clear: both; }
}

Then we float all the divs in our row container and use our gutter function to calculate the space between them.


@mixin nesting {
  padding: 0; 

  & > div { 
    float: left;
    margin-right: gutter();
    box-sizing: border-box;
  }
}

Finally we put both of these together and set the total width of our row to 90% of the page width. We also center the container


@mixin row {
   
    
  width: 90%;
  height: 100%;
  margin: 0 auto;
  @include clearfix;
  @include nesting;
  
}

This grid system can now be used in our SCSS files. We include row on the container, and use the column function to define the widths of our nested elements.



.row-container{
    @include row;
}
.row-container > div{
   width:columns(4);
}

So if we have 3 divs inside our .row-container then this will give us a three column layout. However, if you try this you will notice it does not work quite right. The problem is the last div within .row-container will still have the gutter set on its right margin. This causes the container to break since that adds up to more than 100%. To fix this we have to remove the right margin on the last child element. We can use the CSS :last-child selector for this.



.row-container{
    @include row;
}
.row-container > div{
   width:columns(4);
}
div.row-container > div:last-child{
   margin-right: 0;
}

 

Working with Offsets

Next we add another mix-in for offsetting columns in our rows



@function offset-columns($columns) {
  $margin: $columns * $column-width + $columns * $gutter-width;
  @return $margin;
}

@mixin offset($from-direction, $columns) {
  @if $from-direction == left {
    float: left;
    margin-left: offset-columns($columns);
  }
  @if $from-direction == right {
    float: right;
    margin-right: offset-columns($columns);
  }
}

 

Building a Grid System

This will calculate the offset and can be used for either the left or right side of the row. The grid system will work like this and I used it like this for a very long time. However, I still felt I was missing something. When I compared myself to other developers using systems like Bootstrap or Foundation, I found I worked much slower to get a full responsive page built out. I needed a way to speed things up. The advantage Bootstrap and similar frameworks had over this was that they dont always require you to set styles for every screen size. They have commonly used defaults. For example in bootstrap everything falls down to a single column on mobile automatically, without writing any addition CSS. While this is not always ideal behavior; and in the case of Bootstrap often leads to some poorly designed overlooked mobile sites, It is a useful place to start from. I needed to be able to define column widths while writing the html; like Bootstrap, however I did not want to add extra class names on everything like Bootstrap does. I decided to use HTML5 data attributes to contain my grid styles.



div[data-row]{ @include row; }

div[data-row] > div:last-child{
  margin-right: 0;
}

First we attach the row mix-in to any HTML element with the data-row attribute. I also use this opportunity to fix that pesky right margin on the final element in the row.


@for $i from 1 through $maximum-columns{
    div[data-row] > [data-col="#{$i}"]{
          
          @include breakpoint(small) {
            float: none;
            margin-right: 0;
          };
        
          @include breakpoint(tablet) { width: columns($i); };
          
          
  }
}

Next to generate our column width styles, we run a SCSS loop using the $maximum-columns variable we defined earlier. This code defines our default vertical stack on mobile phones (and is the only place I use the small screen media querys). It then sets the column width based on whatever the data-col attribute is set to.


@for $i from 1 through $maximum-columns{
    div[data-row] > [data-col="#{$i}"]{
          
          @include breakpoint(small) {
            float: none;
            margin-right: 0;
          };
        
          @include breakpoint(tablet) { width: columns($i); };
          
          
  }
  div[data-row] > div[data-desktop="#{$i}"]{

          @include breakpoint(large) {
            width: columns($i);
          }
  }
}

Here we add another declaration for the data-desktop attribute, incase we want to change our column widths as the screen gets larger. I also add these two functions to the loop to handle any offsets.


div[data-row] > [data-offset="#{$i}"]{
         
          @include breakpoint(tablet) { @include offset('left', $i); };
  }
  
  div[data-row] > [data-offset-right="#{$i}"]{ 
         
          @include breakpoint(tablet) { @include offset('right', $i); };
  }

Now our grid system is almost complete we just need to account for nested elements. As we saw earlier, the grid functions have this ability built into them already we just need another loop to set the styles for it. This loop is a tad more complex. We will be taking information from two data attributes instead of one this time, so we will need nested loops. Were going to create an alternate version of data-row; called data-nested. This time it will take a number, which will be the number of columns it is nested inside.



@for $i from 1 through $maximum-columns{
    div[data-nested="#{$i}"] {
      @include row;
      &:last-child{
        margin-right:0;
      }
      width: 100% !important;
      @for $j from 1 through $maximum-columns{
          & > [data-col="#{$j}"]{
            @include breakpoint(small) {
              float: none;
              margin-right: 0;
            };
            
            @include breakpoint(tablet) { width: columns($j,$i); };
          }
      }
    }
}

This code changes the width to 100% (a bit brutishly) and then makes our old data-col attribute work inside nested elements. We use the grid like this.

<div class="wrapper">
      <div class="header" data-row>
        <div class="grid grid-1" data-col="4" data-desktop="6" >Hello!</div>
        <div class="grid grid-2" data-col="4" data-desktop="3" >Hello!</div>
        <div class="grid grid-3" data-col="4" data-desktop="3" >Hello!</div>
        
      </div>
      <div class="body" data-row >
        <div class="grid grid-1" data-col="1" >Hello!</div>
        <div class="grid grid-2" data-col="1" >Hello!</div>
        <div class="grid grid-3" data-col="1" >Hello!</div>
        <div class="grid grid-4" data-col="1" >Hello!</div>
        <div class="grid grid-5" data-col="1" >Hello!</div>
        <div class="grid grid-6" data-col="1" >Hello!</div>
        <div class="grid grid-7" data-col="1" >Hello!</div>
        <div class="grid grid-8" data-col="1" >Hello!</div>
        <div class="grid grid-9" data-col="1" >Hello!</div>
        <div class="grid grid-10" data-col="1" >Hello!</div>
        <div class="grid grid-11" data-col="1" >Hello!</div>
        <div class="grid grid-12" data-col="1" >Hello!</div>
        
      </div>

      <div class="body-row" data-row>
        <div class="grid grid-1" data-col="6" >

          <div class="nesting" data-nested="6">

            <div class="nest-grid-1 grid" data-col="3" >Nested Hello!</div>

            <div class="nest-grid-2 grid" data-col="3" >Nested Hello!</div>

          </div> <!-- end .nesting -->

        </div> <!-- end .grid-1 -->

        <div class="grid grid-2" data-col="6" >Hello!</div>
      
      </div> <!-- end .body-row -->

      <div class="body-row-3" data-row>
        <div class="grid grid-1" data-col="6" data-offset="3" >Hello!</div>
        
      </div>

      <div class="body-row-4" data-row>
        <div class="grid grid-1" data-col="3" >Hello!</div>
        <div class="grid grid-2" data-col="3" >Hello!</div>
        <div class="grid grid-3" data-col="3" >Hello!</div>
        <div class="grid grid-4" data-col="3" >Hello!</div>
        
      </div>
      
    </div>

See the Pen SCSS Responsive Grid by mitchel p Wassler (@MpWassler) on CodePen.  

Awwwards