featureenvy

The blog of a thinkerer.
By @featureenvy

All rights reserved.

How do CSS grids work?

When I started with a new web page I, like (probably?) most of us, didn't start from the very beginning. Why? Because there is a lot of ceremony involved in setting up the basic structure of a web app. We need a navigation. A content area. A header. A footer. And we probably arrange them in a grid. And of course we need to apply standard styling to buttons, forms, tables, and typography. Luckily, we have CSS frameworks that take out most of the guess-work and lay a good foundation for us. While it works, I always wondered how to these grids work? Is it magic?

It' (not) magic!

Let me give you an example. In Twitter's Bootstrap we can arrange content with a few divs:
<div class="row">
  <div class="offset2 span4">
    I'm offset two columns and four columns wide
  </div>
  <div class="span4">
    I'm also four columns wide
  </div>
</div>
<div class="row">
  <div class="offset2 span8">
    And I'm on a new row!
  </div>
</div>
Other frameworks use similar syntax. So... how do they work?

Bootstrap, 960 Grid System

There are a few CSS frameworks around, but I will compare Twitter's Bootstrap and 960 Grid System. I already showed a basic example with Bootstrap. Now let's compare it with 960.gs:
<div class="grid_4 prefix_2">
  I'm offset two columns and four columns wide
</div>
<div class="grid_4">
  I'm also four columns wide
</div>
<div class="clear"></div>
<div class="grid_8 prefix_2">
  And I'm on a new row!
</div>
It works similar to Bootstrap, in that we define the length with "grid_4" compared to "span4". Also we can define the offsets with "prefix_2" ("offset2" in Bootstrap). But there is a difference how we define a new row. Whereas Bootstrap uses an outer div with class "row", 960.gs relies on a clearfix div on the same level. Here I like Bootstraps approach better, because it is easier to walk through the HTML when I can collapse a whole row and don't have to search on possibly hundreds of divs on the same level to find the cell in the middle of the document.

How these grids actually work

Thanks for all the syntax, but I still didn't talk about how the magic happens. Let's look at Bootstrap first.

Bootstrap SPAN

If we take a look at bootstraps source code, we can find a file called grid.less*. The only thing this file does is call the #grid core mixin. There is also an import for the responsive version of the grid, but we will ignore that one for this walk-through.
// Fixed (940px)
#grid > .core(@gridColumnWidth, @gridGutterWidth);
(See also on Github) We can find the core mixin under, surprise, surprise, mixins.less. Bootstrap defines a mixin called .spanX (@index). It recursively creates all the .span12 .span11 etc. classes in the compiled CSS.
    .spanX (@index) when (@index > 0) {
      (~".span@{index}") { .span(@index); }
      .spanX(@index - 1);
    }
    .spanX (0) {}
(On Github) The line (~".span@{index}") { .span(@index); } uses Javascript Evaluation to define the class. The rules will be the LESS mixin .span (line 2), so lets look at this next.
.span (@columns) {
    width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1));
}
(On Github) This sets the width of the .spanX to the width of the columns plus the gutter in between the columns it spans. But wait! That can't be all! We can define multiple spans in a single row! So we have some more CSS!
[class*="span"] {
      float: left;
      margin-left: @gridGutterWidth;
}
(Github) All classes that start with .span will be floated to the left. Additionally, the gutter is applied as margin on the left, so the columns have a bit distance between each other. So that about covers the .span. What about .offset?

OFFSETing Bootstrap

Offsetting works roughly the same as spanning the grid elements:
.offsetX (@index) when (@index > 0) {
      (~".offset@{index}") { .offset(@index); }
      .offsetX(@index - 1);
    }
    .offsetX (0) {}

    .offset (@columns) {
      margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1));
}
(Github) Again, we have the .offsetX-mixin that creates the possible offsets. This will load the .offset mixin (note the missing 'X'), which calculates the, well, offsets (highlighted lines). All the classes I used in the example except .row have been explained. Let's look at the rows.

Bootstrapping rows

This one will be easy:
.row {
      margin-left: @gridGutterWidth * -1;
      .clearfix();
}
(Github) It adds a margin to each row and then loads the .clearfix mixin. The clearfix code requires some trickery since clearfix and browsers tend to hate each other (as described in the comment).
.clearfix {
  *zoom: 1;
  &:before,
  &:after {
    display: table;
    content: "";
  }
  &:after {
    clear: both;
  }
}
(Github)

The Bootstrap Conclusion

So how does bootstrap generate the grid? Four steps are needed: And now we have a grid system!

So what about 960.gs?

I didn't forget! So let's do a check if they use the same system. Go to the 960.gs demo page and see for yourself. .grid_2 is defined as width: 140px and all grid classes are floated to the left and have a gutter applied. The only difference is that 960.gs seems to apply the gutter to left and right, whereas Bootstrap just applies it to the left margin. The "clear" class is a simple clearfix (who would have thought?). The .prefix_X classes have a .padding applied to the left. Again a small difference, 960.gs applies a padding, but Bootstrap relies on margin. So which one is right? ;)

So there you have it

Both systems work roughly the same, with small differences. Both use floated cells with a clearfix to make rows. Both define a cell length and a cell offset. One uses margin, the other padding. Did I miss something interesting? And are there any CSS frameworks out there which use a different system? * Which means Bootstrap is written with LESS, a CSS preprocessor like SASS.