Get Refreshed: Liquid Layouts With Simpler CSS and Without A Semantic Mess
The two-column page design is a staple of web design, providing a convenient blank slate for your site's content to be nicely accented by a useful sidebar. Back in the days of tables, it was easy to implement. With CSS, a major challenge has been keeping content in the proper order (important stuff first), while arranging the columns in either order. Plenty of brilliant solutions have been devised to solve this dillema, and I've made a use of a lot of them. The trouble is, they're just a damned dirty mess.
Stating the Problem
Columns are not inherently difficult in CSS, most of the time. Give the columns a bit of width, float them, clear the float on elements below, and you're all good. (There's plenty of other options, of course, which I'll get into some other time.)
Liquid layouts usually don't go so cleanly, however. Especially in cases where you want your main content (which should come first in your markup) to appear to the right of a sidebar or other column of lesser importance (which should be coming later in the markup).
Where there's a Mt. Everest, though, there's crazy nutjobs who are addicted to frostbite and oxygen deprivation. One of the more convenient, effective solutions that I've seen for liquid layouts appeared in the A List Apart article Creating Liquid Layouts with Negative Margins by Ryan Brill back in June 2004. The solution works brillantly, and I've used it quite a bit. I've had several beefs with it, however:
It uses a lot of floats. And if you want to make your life more difficult when debugging websites, floats are the way to go.
It fills the page's markup with non-semantic divs, which drives me batty. Every time a designer uses an element marked "wrapper", somewhere a CSS sprite dies a horrible death.
The solution just feels like a hack. Solving a problem by adding some nested floated divs feels wrong, like kicking puppies.
Will someone please think of the puppies?!
The Solution: Killing Floats with Inline-Block, and Frugal Container Use
I spoke about this earlier, but one of the most exciting developments in CSS for me this past year was Firefox 3's inclusion of display: inline-block. Thanks to the incredibly fast adoption rate of the new version among Firefox users, this means that inline-block is pretty much global in its browser support. What use is this for us? Simple, it eliminates the need to use of float-left for block elements. It's a small thing, to be sure, but one with great ramifications on simplifying CSS debugging, and for our specific solution it allows us to do some fun margin-related tricks.
When examining Mr. Brill's solution, one thing I was constantly fighting was his number of divs for the solution to be viable. Usually there's an outer container (typically something like "content"), inside which there's two more containers (one for the main content, one for the sidebar), then the main content gets at least one more container as a wrapper to help make the negative-margin technique work properly. He also has a clearing div in there, which brings a total of five divs for a two-column solution. Something like this:
<div id="content"><div id="wrapper"><div id="content_main"></div> </div><div id="content_sub"></div><div id="cleardiv"> </div></div>
That's just too many for me.
I decided to aim for a solution that retained the outer "content" div, as that felt like a logical, semantically appropriate container for everything inside it. Additionally I kept containers for the main content and secondary content, as each column was a logical section. I got rid of the wrapper, as I detest extra layers of divs wrapped around something for purely presentational reasons, and the clearing div, as I hate empty divs even more. My end result is this:
<div id="content"><div id="content_main"></div><div id="content_sub"></div></div>
Then I hit my head against a wall for a long time until jelly came out.
Brain jelly and peanut butter sandwiches are not very tasty, in case you were curious.
The biggest issue is as follows: With a liquid layout, we've got a single fixed-width column (in this two-column case), and then we've got the second column which has to expand or contract to the size needed to fill the remaining space. The fixed column is easy to generate. The other is trickier, as there's no CSS rules for width: 100% - 300px. Although, now that I wrote that down, I wish there was. Mr. Brill went ahead and made his flexible column's width 100%, and then using negative margins sucked the stationary content to overlap it. To prevent the guts inside the flexible content from literally spilling over the sucked-in column, he gave that content (in a second wrapper inside itself) a positive margin that made empty space that coincided with the width of the other column. It's messy to look at the CSS, and frankly the combination of CSS makes my brain spin a bit, but it worked, and did so consistantly.
However, without that extra wrapper, I wasn't sure how to both pull the columns next to each other and make sure there was no overlaps.
The Epiphany: Thinking Outside the Column
Finally the smashed remnants of my cerebral matter came to a strange sort of epiphany while I was listening to Katy Perry on Pandora. Well, two epiphanies.
First, I don't care if she's another brainless giggling pop singer selling music with sexual postering. I find her music entertaining.
Secondly, I had another div I could work with - the content div in which I was containing the two column-specific divs.
What I did, then, was to give the content a padding on the side that will contain the sidebar, equal to that column's width. I then give both columns display:inline-block (and applied the IE-specific fix for that style, as noted in my blog post linked above about inline-blocks). I then gave the main content (which in this example is the fluid column) a width of 100%. It now takes up all the space inside content except for the padding, which is where the sub content will go. The sub-content, thanks to inline-block, wants to sit next to the main content. But due to the combined width of the content's padding and the main_content, it can't fit and is ejected to beneath the first column. However, if I give it a negative-margin (yes, this technique still uses that, albeit in a different way) to the direction I want the sub-content to fit, it causes it to suck itself into place.
Here's an example, with the stylesheet here (and the IE-specific fix stylesheet here, which fixes IE's implementation of inline-block and corrects hasLayout issues). If your main content (which is appearing first in the markup, right?) is the left column, then it's as simple as that.
If you instead need to have the main content appear to the right, you'll need to make some modest, minor tweaks. Obviously first you'll flip the direction of the padding on content and the negative-margin on the sub content, but you'll also need to add float:right to the main content. Because floats are involved, we need to clear the float, using either an overflow:hidden on the containing content div or putting clear:both on the following footer. (IE6, always the troublemaker, also requires you to use float:left on the sub content, for inexplicable reasons, but conditional comments allow you to easily correct for that single browser.)
Here's a second example, showing the reversed columns. The IE-specific corrections are the same (minus the mentioned IE6 issue), but here's the stylesheet for the second example, where you'll see the small addition of the float as well as the reversed paddings/margins.
This isn't the perfect solution that will solve all problems. Faux colums will be needed if you wish to make the columns to appear to be the same height, and we still have the one content div that the columns are contained in. Still, we've removed any non-semantic divs, taken away any reliance on floats in situations where they aren't called for (we still use a float for when the columns are reversed, but that's in spirit with the desired use of that style), and have set up a situation where our content is preserved in the proper order at the same time.
As an after thought that deserves it's own blog post (more on this in the future), I think that with the new maturity of inline-block in cross-browser support, we need to stop using floats for column solutions, except when the appropriate situation comes up that demands it. We're not quite to the magical era of peace and happiness that is full CSS3 support, but there's plenty of CSS 2.1 optimization techniques we can use to get cleaner, more robust solutions to common design problems. We just need to dust off the old styles and take a look at what support has become available in the last few years.