1700Days

1700 days since this post was last revised. Specific details are likely out of date.

The issues I’m about to describe aren’t particular to grid systems, these ‘sub-pixel’ rounding errors can affect any layout you want to work cross browser. I’m also not picking on IE here, this issue is exhibited when comparing the latest builds of Firefox, Safari and Chrome…

Be aware that now, as of V1.06/March 2013, Susy also provides a mixin (@isolate-grid) to create container relative elements (if you don’t understand that already, read the post in full and you will). Therefore check out the Susy docs. Also, the technique is covered in my book, Sass and Compass for designers

There are a few grid systems out there for Sass & Compass and they make creating responsive grids a whole lot easier. Typically you pass the number of columns you want an item to be and the grid system converts it to percentage values for you (with all the complicated mathematics I despise being handled automatically). I’ve long been a fan of Eric A. Meyer’s ‘Susy’ grid system for Sass & Compass. It’s served me well on numerous projects.

First a (very) simple explanation of how Susy works; it lays things out in a row one after the other with the final item (the ‘omega’) floated to the opposite side of the other row items (default of right for left to right layouts). The positioning of each grid item is in relation to it’s nearest neighbour.

The syntax of a grid item is this:

@include span-columns(1,7);

In the prior example, each of the items that use that mixin are 1 column wide (of a possible 7). The final item gets an ‘omega’ argument to float it right and remove the right margin:

@include span-columns(1 omega, 7);

Sadly, as good as this system is (and believe me, it’s a thing of beauty) browsers introduce some issues. Each converts percentages to a pixel value (when rendered to screen) but round any sub-pixels differently; some round up, others round down and some do even cleverer stuff still, splitting up the left over pieces and magically spreading them between items in a row. The differences in the way browsers handle this manifest themselves frustratingly. Whilst some handle it gracefully (take a bow Firefox and Chrome), some browsers (notably Safari on desktop and iOS for example) display a large gap at the end of grid items. Why?

A basic example of the problem

Here’s a basic example. If you imagine a row of seven items, each with a padding value as a percentage that the browser, once converted in the browser to pixels, rounds down, it can produce a 4–5px gap on the right most item. Now, I’m not anal about the odd pixel here and there (it’s part of the ebb and flow of web design), but 4–5px does look odd. So what do we do?

In the defence of browser vendors, rounding up would be worse as it would almost certainly break the layout horribly, something that used to happen with OldIE and as I understand it, the W3C CSS spec doesn’t define how vendors should round sub-pixels so some have chosen to round down, others not.

Responsive Design’s Dirty Little Secret

Others have tackled this issue and how it relates to responsive web design before, John Albin Wilkins has written a great piece on the issue, ‘Responsive Design’s Dirty Little Secret’. Furthermore, he also offers a solution. His grid system is called ‘Zen Grids’ and does things fundamentally different. I’d encourage you to read his blog post but here’s the elevator pitch of the way Zen grids work:

Instead of each item being laid out relative to it’s nearest sibling (as Susy does), it uses a negative margin trick to lay it out relative to the inside edge of it’s parent element (the containing element the items live in). This means the majority of the rounding issues are negated (you’re only suffering a single rounding error rather than multiples all compounding the visible pixel difference issue). You may get the odd pixel gap but it’s more than acceptable. So that’s the answer then? Just use the Zen grids instead? Not so fast Batman…

The (notable) problem with Zen grids

Zen grids are robust and have been battle tested for some time. However, depending upon what you are doing, there are limitations. Let’s suppose you are laying out something with Zen. Here’s the syntax for an item in a grid:

@include zen-grid-item(1, 3);

Now, be aware that the syntax of the arguments passed to the Zen grid mixins are not the same. This doesn’t mean the item spans one column of a possible 3 as it does with Susy. Instead it means that it spans 1 column and begins at position 3 (the number of columns in a grid is declared by a variable at the outset). And herein is the rub of the Zen grid approach. With Zen grids it’s necessary to define the location of each item in the grid. For example, let’s say you have a row of 7 identical elements, each with the same class (for argument let’s be creative and call it ‘.item’). With Susy you can do this:

.item {
    @include span-columns(1,7);
    &:last-child {
        @include span-columns(1 omega,7)
    }
}

Each item will be 1 column and the final one will be floated right. Nice and compact code. However, with Zen you need to define each item. For example:

.item {
    &:nth-child(1) {
        @include zen-grid-item(1, 1);
    }
    &:nth-child(2) {
        @include zen-grid-item(1, 2);
    }
    &:nth-child(3) {
        @include zen-grid-item(1, 3);
    }
    &:nth-child(4) {
        @include zen-grid-item(1, 4);
    }
    &:nth-child(5) {
        @include zen-grid-item(1, 5);
    }
    &:nth-child(6) {
        @include zen-grid-item(1, 6);
    }
    &:last-child {
        @include zen-grid-item(1, 1, right);
    }
}

Note: the :last-child situation with Zen is also a little odd it you are used to Susy. As it comes from the right (you’ll notice I’ve passed the ‘right’ argument on the last-child) it also takes the first position (the first position coming in from the right).

Now, this extra code isn’t necessarily a deal breaker, plus as we are using Sass we can do a @for loop to handle an unlimited amount of grid items. Here’s the prior example done with a @for ‘loop’:

.item {
    @for $i from 1 through 6 {
        &:nth-child( #{$i}) {
            @include zen-grid-item(1, $i);
        }
    }
}
.item {
    &:last-child {
        @include zen-grid-item(1, 1, right);
    }
}

Whilst that makes the authoring of the styles much DRYer it doesn’t negate more bloated CSS being produced (you’re still getting a separate nth-child rule generated for each item in the row). It also isn’t very flexible if you don’t know what items will ‘live’ in the grid (but obviously great if you do). Finally, it’s worth noting that Zen adds padding (the ‘gutter’ of the grid) on either side of the grid items (where Susy adds it to a single side) so if you already have padding on your containing elements you’ll need to think about that.

I also created my own ‘mini-grid’ system, called ‘bb-grid‘ to solve the padding on either side of elements issue with container relative positioning. You can check out the post about it here. Also, remember that Susy now also (as of 1.06/March 2013) has this technique baked in so it’s not necessary to combine Zen and Susy

So, it seems we are left with a choice of two poisons:

  • Susy: clean markup but potential visual anomalies
  • Zen: verbose markup but significantly less visual anomalies

It’s also important to note that neither system is at fault, they both solve lots of bigger problems and I’m certainly thankful to their respective authors – it’s those pesky browser vendors who are to blame!

Hacky options…

What I’m about to suggest it a hack. No two ways about it. It’s possible to cheat the Susy layouts and reduce the apparent visual anomalies only on the browsers (iOS and Safari desktop in this instance) that are the worst offenders. It’s an embarrassingly brittle and hacky technique and is no doubt going to make things go squiffy. Hopefully that’s enough of a disclaimer! However, if all else fails and you are happy to break out the ‘Big Hammer’, here goes…

Modernizr can feature test lots of things, I use it on most projects as a way to fork code when needed and I’m suggesting abusing those capabilities here. Modernizr developers (Faruk Ates, Paul Irish, Alex Sexton, Ryan Seddon & Alexander Farkas) look away now…

Perhaps use a feature that Chrome and Firefox have and Safari (desktop and iOS) doesn’t. According to the caniuse.com tables ‘Indexed DB’ fits this purpose (although, having tried this, although Firefox supposedly supports Index DB, Modernizr reports it as ‘no-indexeddb’ – probably certainly for some reason I don’t yet understand).

However, whatever feature you settle on, with a differentiating feature in hand, we can get Modernizr to add a class to our HTML element, based on whether the browser supports the feature (say ‘indexed’ and ‘no-indexeddb’ respectively). If you are using Susy, you could therefore do this with your last child element:

.item {
    &:last-child {
        @include span-columns(1 omega, 7);
        .no-indexeddb & {
            margin-right: .5%;
        }
    }
}

Although the Susy mixin defines a margin of 0 we use a little specificity and the Sass parent selector (&) to target the Modernizr added class to add a little margin to the right (or however many pixels you need) of the last child, hopefully removing a little of the apparent space in your design.

There, didn’t tell you it was elegant did I? I’ve had limited success using the technique and in the end decided against it. However, as situations differ, this hack may help dig you out of 4–5px hole in some instances.

Eric A. Meyer also provided a few pointers on how it can be negated (in a far more rational and less ‘nuclear’ way than I suggested). Probably best to try those first!

No best solution

At this point, as far as I’m aware (and PLEASE tell me if anyone knows otherwise), there is no clear cut solution to the sub-pixel rounding issues. Depending on what you need to achieve, it’s a case of choosing what solves most of the problems you have.

Hopefully, in time, browser vendors will settle on a uniform way of sub-pixel rounding and this problem will go away.

Finally, just a note of ‘Kudos’ to Eric A Meyer and John Albin Wilkins for at least providing two possible ways to easily layout out items in a flexible grid.

Ben Frain Developer, Author: 'Enduring CSS', 'Responsive Web Design with HTML5 & CSS3'.