Responsive Design’s Dirty Little Secret
by John Albin Wilkins
The truth is that fluid grids are broken. Well… perhaps just cracked a bit. Responsive Web design, as Ethan Marcotte defines it, is simply a fluid grid, fluid images and media queries. But fluid grids have a dirty little secret: rounding errors. As we lay out our columns in percentages, browsers have to translate that into actual device pixels to fit in the viewport. And Chrome, Safari, other WebKit browsers, Opera, and the usual suspects (IE 6 and 7) all produce “errors”.
I put errors in quotes, because the issue actually has to do with the CSS spec. It doesn’t specify how browser vendors should deal with percentages that contain decimal place precision. For example, with a 6 column grid, each column is 100% ÷ 6 = 16.666667% wide. On a 1000 pixel wide viewport (which I’ve conveniently picked to make our math easier), that calculates to 166.66667 pixels per column. Since the spec gives no guidelines, browser vendors are free to make their own rules. If a browser were to round to the nearest pixel, in our example, we’d arrive at 167 pixels. But since 167 x 6 = 1002 pixels, we’d no longer have room for all 6 columns in our viewport. Alternatively, if a browser rounded down to 166 pixels per column, we’d be 4 pixels short of perfectly fitting all columns into our viewport.
IE 6 and 7 took the former strategy, which meant layouts often broke completely as the final grid item in a row ran out of room in its parent container and was forced to the next row. This failure is legend. See figure 1. Taking the later strategy, Webkit browsers calculate all the values and then round down to the nearest whole pixel. Thus, avoiding the potential for layout breakage. Opera, on the other hand, simply takes the 16.666667% value from our stylesheet, rounds it down to 16% first and then does the calculations to arrive at a 160 pixels per column, a full 6 pixels off of what you’d hope. Before you start swearing at the Opera developers, remember again that the CSS spec provides no rules for “the right way” to do any of this.
How bad could it get?
Unfortunately, if you use percentage-based gutters and padding, the problem gets even worse. Most layout methods in CSS float items next to each other. Which means the exact positioning of a grid item is dependent on all of the calculations done on the items before it in the row. If you had a 12 column grid with percentage-based margins and padding, that means the 12th column in a row is dependent on the margin-left, padding-left, width, padding-right and margin-right calculations of its 11 preceding columns. Add the margin-left of the 12th column itself and that's 56 opportunities for rounding errors while determining which pixel the 12th column should be placed at. In other words, your 12 column could theoretically be 56 pixels to the left of where it should be. No wonder, I’ve seen front-end developers swearing in Twitter.
Given this shocking little fact, it’s no wonder that so many of the examples of responsive design on mediaqueri.es have no obvious “box” wrapping designs that might make the flaw obvious in some browsers.
To explore these errors yourself, you can view the 10-column demo page that exposes the rounding errors in various browsers.
What the %%%% do we do?
First off, let me address all of you adaptive Web design advocates who are jumping up and down screaming, “I know! I know!” Yes, using an adaptive design approach instead of a responsive one is an obvious work-around. Adaptive Web design doesn’t use a fluid grid, so it’s completely immune to fluid grid rounding errors. Instead adaptive designs pick a series of specific viewport widths they support and those viewports that don’t exactly match those widths get the fixed-pixel layout for the next smallest layout that the design does support. I won’t get into the debate of adaptive versus responsive, I just wanted to mention that adaptive design is a perfectly fine work-around.
The best solution
You’ll notice that I didn’t include Firefox in the list of browsers with rounding problems. Firefox implements a rather innovative solution called sub-pixel rendering. Instead of rounding everything to whole pixels and allowing the errors to compound, Firefox will retain the sub-pixel values for all CSS properties and when an element's positioning depends on the properties of previous elements, the sub-pixel values will be used in the calculations. The effect is much more pleasant and inline with designers’ expectations.
IE8 introduced sub-pixel rendering apparently in reaction to how poorly they handled layouts with their former “round up” strategy in IE7 and earlier. WebKit and Opera, with their “round down” strategy, have yet to adopt sub-pixel rendering.
One option is that you could wait for all the browser vendors to replicate the sub-pixel rendering solution, but we’ve have been waiting for over 4 years for that to happen. I’m looking forward to the fix, but I’m not waiting for it either. Fortunately, there’s three workarounds you can implement today.
Wait…what about CSS3 flexbox?
Since we’ll still be using percentages to implement a responsive design with flexbox, it will still be susceptible to rounding errors. Flexbox is not a layout panacea.
Remove some of your percentage values from your design
First off, admit it, dealing with percentage-based gutters and padding is only fun for the truly anal-retentive. Our font sizes are not varying based on the viewport size (usually) and, in order to achieve a proper sense of aesthetics, our gutters should be proportional to the text size. Having gutters that expand as the viewport size increases means we have to add a small battalion of minor breakpoints to force the gutters to behave.
But there’s a little known CSS property called
box-sizing: border-box. Palantir’s own Patrick Grady showed me this property last summer and it changed my life. In the CSS2 box model, padding and borders are external to an element’s width. This meant that if we set an element’s width to a percentage of its parent’s width, we were forced to also use percentages for the margins and padding of that element. Otherwise, the math just wouldn’t work out. 4 columns at 20% plus 30px gutters can never equal 100%.
With border-box, you change the box model for that element so that padding and border are now internal to its width. This means you are no longer forced to use percentages and you can now use a fixed a rem, em or pixel value for padding (that I would recommend be proportional to your content; e.g., the font-size). The compromise here is that you have to use padding for your gutters instead of margins. But I feel the advantage of making your gutters proportional to your content outweighs the disadvantage of needing a wrapper element when you want the edge of a styled box to align to a grid line. The former is needed all the time, the latter only occasionally.
So, when using border-box, you no longer have rounding errors from margins (because you’ve stopped using them for gutters) and from padding. However, there’s still rounding errors on the widths. But we’ve reduced the theoretical rounding error on our 12th column from 56 pixels down to 11 pixels. Better, but the result is still noticeable to most users.
Don’t allow rounding errors to compound
The real problem with rounding errors is their compounding nature when doing layout. In order to calculate the left edge of a grid item, we have to add up all of those values of its sibling grid items. What if we could instead just specify the left edge directly? In other words, could we just specify the distance from the left inner edge of the parent container while still allowing the item to affect the flow of the rest of the document?
It turns out you can. I’ve been using a technique since about 2004 that allows “container-relative floats”. It’s also the primary layout mechanism used in Drupal’s Zen theme which has over 700,000 downloads, so, though it may initially look too clever by half, it is extensively tested and solid.
The basic recipe to get container-relative floats is to apply this CSS to each grid item within the containing element:
Then, for each grid item, simply specify the exact distance you want from the container’s left edge, using margin-left.
Here’s a quick example of this “negative 100 percent”
width: 40%; /* 2 columns in a 5 column grid */
width: 20%; /* 1 column in a 5 column grid */
(Note that you can also position a grid item relative to the right edge of the container by floating it to the right and using a positive margin-right and a -100% margin-left.)
How does this work? First, recall that percentage-based margins are relative to the containing block’s width, so “-100%” means we’re using the width of the container. Next recall that the right margin of floated items affects other items floated next to it. If you use were to use margin-right: -10px, adjacent floats would be relative to this right edge minus 10 pixels. Logically, “-100%” means adjacent floats would be relative to the right edge minus 100% of the container’s width. This should mean that the “relative edge” is well to the left of the container’s left edge. However, the container’s left edge will prevent adjacent floats from even seeing the relative edge when it is so far outside the container. Long story short, each adjacent float no longer sees the right edge of its siblings; it only sees the left inner edge of the container.
If you think this sounds an awful lot like absolute positioning, you’re missing the crucial difference. Absolutely positioned items are completely outside of the flow of the document and have no way to affect the positioning of other items. But container-relative floats can still be “cleared”. This is the crucial point. While floated items with this technique can no longer see each other’s right edge, they can still see their bottom edges when clearing. And it means you can start a new row of grid items by simply clearing the previous grid items and this new row will be positioned below all the previous content. That’s not possible with absolutely positioned items.
Since our grid items are no longer affected by the adjacent edges of its siblings, we are no longer as constrained by source order. What prevents us from putting the first item in our HTML source in the middle grid column and then randomly distributing the next items to the left or right of the first? Absolutely nothing. Within a single row of grid items, the HTML source order no longer defines the visual source order. All without the need for “push” and “pull” classes found in layout methods like 960.gs.
So… did we fix all our rounding errors? Not quite. We’ve reduced our positioning calculations to just one value. But that one value is still subject to a rounding error, which means we will sometimes see a one pixel error. Fortunately, due to the non-OCD nature of most website users, this one pixel error is usually unnoticeable.
If your content is quality, no one will notice a one pixel design error.
But there‘s one last work-around you can implement to reduce even this one pixel error.
Fixing the last one pixel error? Cheat.
If all of your grid items are floated left, all of your left edges should align. So you'll likely see that one pixel error on the right side of the right-most grid item. If this right edge is supposed to align with other items in other rows, this one pixel error still might be noticeable even to non-designers. But guess what? We can align all of those right edges perfectly if we float all of those items to the right instead of floating them left. Guess which browsers give a rounding error when calculating
margin-right: 0%? Not even IE6.
That work-around will place the rounding error in the middle of our page where its less noticeable. More generally, you have some level of control over where the rounding error occurs, so place it where it’s less of an issue.
I recently wrote a manifesto about why building responsive designs requires a CSS preprocessor like Sass. Using Sass simplifies a lot of the layout design we need to do, while simultaneously hiding the complexity we would otherwise have to deal with when writing in raw CSS.
So how does Zen Grids deal with these rounding errors? With Zen Grids, the
box-sizing: border-box is the default gutter method and the container-relative floats are done automatically for you. And changing the direction of the floats for a grid item as easy as adding “right” to its zen-grid-item() mixin.
Since I’ve been using Zen Grids (and its predecessor) for years, I wasn’t even aware that rounding errors were so much a problem for fluid grids until about a month ago.
And now, with these techniques, rounding errors won’t be a problem for you either.
Additional information on how to create responsive sites using Zen Grids and the Zen 5 Drupal theme can be found in this hour-long webinar I presented with the folks at Acquia on July 19: http://www.acquia.com/resources/acquia-tv/conference/creating-responsive...