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:
  • Create all possible .spanX classes, where the length is calculated based on column width and gutter. Also float them to the left.
  • Create all possible .offset classes with the length calculated from column width and gutter.
  • Create a .row class that acts as a clearfix.
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.

Why the Rails Conventions Are Great

One of the best features of Rails is that is has an opinion. A strong one at that. Anyone coming from the world of Java and J2EE can tell you that having a lot of options isn't always the best thing. Sometimes, a simple convention might be a lot easier to follow than defining hundreds of XML files.

I Love Convention Over Configuration

For example, Rails' ORM, ActiveRecord makes it possible to add columns to the database without changing the model. Which makes it a lot easier to program with. Of course today J2EE has JPA annotations, which come close to that, but I think they still are trying too hard to fit into legacy databases.

Rails is made for the 99% of all use cases, and it doesn't hide that fact. Which isn't wrong, because, well, 99% is a lot after all.

Ease Of Use

One of the amazing things that all this convention gives us is that picking up a new app is really easy. If I look at a Rails app, and a form is wrong, I can simply look at it and change it, there is no need to search where the form is in the app. In J2EE everyone is OK with using something totally different in their stacks. No app looks like the next, so picking it up is really hard and requires a lot of time. J2EE does have "Enterprise" in its name for a reason. I have worked on a huge J2EE app with a huge team, and it was great! But the knowledge needed to write one such app is immense, the overhead big. It is just not made for small development teams.

Think About the Developers

The structure also helps the 501 developers (and, to be honest, all the rest of us), because a huge amount of knowledge backs all this convention about how web apps should be developed. Something one person alone, or a single group, can never match. You don't know where this piece of code should go? No problem, search for it on the web, someone will know (or at least have seen that problem before. Maybe even solved it in a better way that you are thinking about now). This makes everything so much easier. Today, speed is really king. And Rails is fast. Really fast. Getting a site up and running isn't a problem, because there is no need to first assemble the stack yourself. Solving dependency problems. Find out how these two libraries can work together. Everything just works! A demo app will be up in no time.

Your own stack (probably) sucks!

Making your own web development stack is a lot of work. Library X only works with version 2.3 - 3.1 of library Y? But you need 3.2 for library Z? What should you do? How do you keep all the libraries up to date? What about vulnerabilities? Your glue code between two libraries breaks, what now? How should this new guy know all the guidelines you never wrote down? Having your own stack gives you a lot of freedom, but it is also a lot of work. And a mess is already coming... If you have tried it, what are your experiences? I really think, after having done both, but having such well-defined standards and conventions as Rails has does make it a lot easier. Often I thought "This is easier if I do it this way" only to find out later why others didn't do it this the hard way. I'm still waiting for others to pick up what Rails did instead of just copying it. Using the language and tools surrounding it to the fullest advantage. Is there such a thing in beside Rails?