The Solution to Fluid Inconsistencies and Equal Height Columns

Update: There is a follow up article to this one addressing issues raised in the comments.

An introduction

Being an early adopter of Web Standards back in 2001 and thanks to Jeffrey Zeldman’s Designing with Web Standards released in 2003, I strived for a life without tables and 1 pixel shims and instead one filled with semantic markup and a clean separation of content from presentation. Early on in the transition to Web Standards came frustration, the things we used to do with tables without batting an eyelid became a series of CSS hacks and erroneous markup.

To this day there are two core concepts which still make us standards freaks wince. The first is equal height columns and the second is percentage width consistency across browsers. Chris Coyier of CSS Tricks recently rounded up the current solutions for Fluid Width Equal Height Columns but all have their limitations. We have a new flexible box model in CSS3, however it will be a number of years before we can use this in practice due to the slow uptake of new browsers.

A couple of weeks ago, I decided that enough was enough and that it was time to sit down and come up with a solid answer to the problem. Combining a number of CSS tricks that I have amassed over my years in the industry, I have come up with what I believe to be a solution.

Fluid Inconsistencies

Just today I read a post by Steffan Williams on his frustrations with “Fluid Inconsistencies“, so I will address this first.

As documented by John Resig, the reason for inconsistencies with percentage widths comes down to the fact that different browsers round in different ways. Webkit rounds down to the nearest pixel, IE rounds up and Firefox does a bit of both in order to spread the difference across the containing element.

Surprisingly, Webkit’s rounding causes me the biggest problem. As an example, lets say we have 12 equal width columns butted up together. When the container is at 800 pixel in width, in an ideal world each column would be 66.666 pixels, the problem being that there is no such thing as a fraction of a pixel, so Webkit rounds each column down to 66 pixels. This gives a total width of 792 pixels meaning there is a gap of 8 pixels between the right of the container and the right of the last column.

So what are our options? Absolute positioning could work as we would manually set the left edge of each column, negating the cumulative rounding problem. The obvious problem is that absolute positioning would take the columns out of the flow of the document meaning we couldn’t have anything below them and they wouldn’t force their container to grow. What we need is absolute positioning only on the x axis so that we can position each column a specific distance from the left edge. We can achieve this effect with a bit of css trickery…

Absolute positioning only on the x axis

To simulate absolute positioning on just the x axis, we will combine floats, negative margins and relative positioning. Relative positioning is like absolute positioning in that you can specify a left and top value, however it differs in that the element is positioned relative to where it would have been without relative positioning. If we could just get all the columns to start at the same x position, we could use relative positioning from there to shift the columns into the correct positions.

Given the following markup and bearing in mind that you should normally use semantic class names instead of col1, col2 etc.


<div class="content">
  <div class="col1"><p>I am column 1</p></div>
  <div class="col2"><p>I am column 2</p></div>
  <div class="col3"><p>I am column 3</p></div>
  etc...
</div>

I turns out that with the use of floats and negative margins we can get all the columns positioned at the same starting x position.


.content        { position: relative; overflow: hidden; background: #f5f5f5; }
.content div    { float: left; margin-left: -100%; position: relative; width: 8.333%; }

Note the “overflow: hidden;” on the container, this serves two purposes. The first is to ensure it expands to the height of the tallest float (a simple clearfix), the second is so that IE, who we have established rounds up, doesn’t overlap the right edge of the container. Also note that the column width is set to 8.333% (100% ÷ 12).

At this stage we have all columns stacked on top of each other and positioned at -100%. The reason that they are now all in the same place is that when the first element is shifted to the left by 100% (due to the negative margin), the second element’s x coordinate is now 0, it in turn gets shifted left by 100% and so on.

This leaves us in a great position (pardon the pun) as we can now position the columns anywhere horizontally by just adding 100% to the desired x position.


.content .col1   { left: 100%;     background: #eee; }
.content .col2   { left: 108.333%; background: #ddd; }
.content .col3   { left: 116.666%; background: #ccc; }
.content .col4   { left: 124.999%; background: #bbb; }
etc...

View example 1

The real beauty of this technique is that the column order is independent of the order they appear in the markup. You can swap 1 with 3, 9 with 2 etc. you can even span multiple columns by doubling the width. Below is an example of changing the column order.


.content .col3   { left: 100%;     background: #eee; }
.content .col2   { left: 108.333%; background: #ddd; }
.content .col1   { left: 116.666%; background: #ccc; }
.content .col4   { left: 124.999%; background: #bbb; }
etc...

Notice how all we have done here is change the class names around.

View example 2

So there you have it, we have addressed the “Fluid Inconsistencies” problem and it works in Safari, Chrome, Firefox and right the way back to IE6. The keen eyed among you will have noticed the occasional 1 pixel gap between the columns on scaling the browser window, I will address this in the next section.

Equal Height Columns

This has long been a subject for debate and is a hot topic for the “Just Use Tables!” clan. Something caught my eye when reading Chris Coyier’s Fluid Width Equal Height Columns and that was the “Nicolas Gallagher Method”. He basically uses “:before” and “:after” pseudo selectors to add blank content to the container element which can then be styled, pure genius. The only downside to his technique however, is that you are limited to 3 equal height columns.

Based on Nicolas’s method, I found that it was possible to add “:after” pseudo selectors to the columns themselves to achieve the effect of equal height columns. The benefit of this is that we can tell the styled content to inherit the background properties of it’s parent element. This way you only have to style the column itself and all will be inherited correctly.

So how do we do it I hear you shout! Well it’s almost disappointingly simple, I will show the code and then explain what is going on.


.content            { z-index: 1; }
.content div:after  { content: ""; position: absolute; z-index: -1; background: inherit;
                      left: 0; top: 0; right: -1px; bottom: -9999px; }

As with Nicolas’s method, we setup an “:after” with blank content, position it absolutely and set it’s z-index to -1 so that it appears under the column element who’s z-index is 1. The difference comes with the actual positioning. As our column is positioned relatively, we set it’s left and top to 0 so that the top left corner matches that of the column. We set the the right to -1px to solve the occasional gap between columns as promised. The bottom value is where the magic happens, we set it to -9999px. This makes it drop way below the bottom of the column and as long as the difference in height between the tallest and shortest columns is less than that value, you are fine, a pretty safe bet I would say.

View example 3

And that’s it, equal height columns with flexible widths. We could end here, as everything works in Safari, Chrome, Firefox and IE9. In IE8 and below, the page will degrade gracefully and just not have equal height columns. We can however add IE 6/7/8 support using CSS expressions and I would recommend including the fixes with conditional comments should you need to support these versions.

Fixing Internet Explorer IE6/7/8


/* IE 6/7/8 fixes */
* html .content { height: 1%; /* IE6 hasLayout */ }
.content div    { clear: expression(innerHTML += '<u class="ieafter"></u>', style.clear = 'none', 0); }
.ieafter        { width: expression(parseInt(parentNode.offsetWidth) + 1 + 'px');
                  height: expression(parseInt(parentNode.parentNode.offsetHeight) + 'px');
                  background-color: expression(parentNode.currentStyle.backgroundColor);
                  background-image: expression(parentNode.currentStyle.backgroundImage);
                  background-repeat: expression(parentNode.currentStyle.backgroundRepeat);
                  background-position: expression(parentNode.currentStyle.backgroundPosition); }

View example 4

Things to consider

When using this method, if you need padding on columns, I would suggest adding it to an element within the column as opposed to the column itself as otherwise Webkit has two more opportunities to round down (one for the left padding and one for the right). If you really need to add padding to the columns themselves then you may want to add the following Webkit fix.


/* If we have left and right padding on columns, Safari has 2 more opportunities to round down so compensate here */
@media screen and (-webkit-min-device-pixel-ratio:0) { .content div:after { right: -3px; } } 

Something else to consider is explicitly setting the z-index for each column in order from left to right as the additional width we are giving our “:after” content has the potential to overlay the next column. With the z-indexes it looks like this:


.content .col3  { left: 100%;     background: #eee; z-index: 1; }
.content .col2  { left: 108.333%; background: #ddd; z-index: 2; }
.content .col1  { left: 116.666%; background: #ccc; z-index: 3; }
etc...

View example 5

You will notice in my examples I have used divs. These can be easily substituted for more semantic HTML5 elements such as “section” but I wanted to keep the examples simple without the need for HTML5 shivs etc. My use of ‘col1, col2′ etc. class names are purely for this example, you should use more semantic class names in real development.

I hope you like my new technique, feel free to leave a comment with any questions.

Acknowledgements

Update 08/12/10: As pointed out by Paul O’Brien in the comments, the equal height column technique shown here suffers the same side effect as the One True Layout solution, in that if you have a link to an anchor in a column, that column will scroll when the link is clicked. If you need to link to anchors in your columns, this may not be the best technique and the only other option without adding additional markup is to equalise the column height with javascript. The only benefit of this technique over the One True Layout option is that it fixes gaps between columns. I will continue my search for a solution to the anchor issue.