A List Apart

Menu

Multi-Column Layouts Climb Out of the Box

Issue № 232

Multi-Column Layouts Climb Out of the Box

by Published in CSS, HTML, Layout & Grids · 140 Comments

We all know the problems that arise when we try to build multi-column layouts in which the columns are of equal height. It has been well documented elsewhere, so I won’t go into the details here.

Article Continues Below

A project I recently worked on required an elastic layout with two columns of equal height, each with a different background color. As usual, there was no way to tell which column would be taller. I immediately thought of Dan Cederholm’s Faux Columns, but I needed an elastic layout. I also looked at the One True Layout, but this seemed buggy and required too much extra markup and too many hacks for my taste. I even thought about using JavaScript to make sure the columns were of equal height, but that just felt wrong. Out of desperation, I almost (gasp!) used a table.

But no, there had to be a better way. I just needed to think “outside the box.” And what is outside the box? Borders. If I could float my “sidebar” (or “rail”) div over the border of my “content” div, I could simulate equal-height columns no matter which one was the tallest.

If this sounds familiar, that’s probably because a version of this method was introduced by Douglas Livingstone and extended by Holly Bergevin and John Gallant at Position Is Everything. While relying on the same core theory, the method presented here uses cleaner, less deeply nested markup, includes elastic and two-column layout variations, and generally benefits from having been developed in the post–One-True-Layout world. Here is how it’s done. (Line wraps marked » —Ed.)

The HTML:

<div id="container"> 
  <div id="content">This is<br />some content</div> 
  <div id="rail">This is the rail</div> 
</div>

The CSS:

#container{
  background-color:#0ff;
  overflow:hidden;
  width:750px;
}
#content{
  background-color:#0ff;
  width:600px;
  border-right:150px solid #f00; »
  /* The width and color of the rail */
  margin-right:-150px; /* Hat tip to Ryan Brill */
  float:left;
}
#rail{
  background-color:#f00;
  width:150px;
  float:left;
}

I created a right border on the content div of the same width and color as the rail, then floated the rail div over it. The margin-right:-150px on the content div allows the rail div to move into the newly vacated space. If the content div is taller than the rail div, the border grows with it making it appear that the rail is growing. I set the container’s background color to match the content so if the rail div is the tallest, the container grows with it and it appears that the content column is growing. It only takes a small change to the CSS to make the rail left-aligned, whether the content or the rail is taller.

See it in action or check out the elastic version; try changing your font size and watch the layout change with it.

Three columns: three colors

The three-column layout takes a slightly different approach: the borders are applied directly to the container div. (I could have done this with the two-column layouts as well but I didn’t think of it at the time. Line wraps marked » —Ed.)

The HTML:

 <div id="container">
   <div id="center">CENTER<br />COLUMN CENTER</div>
   <div id="leftRail">LEFT RAIL</div>
   <div id="rightRail">RIGHT RAIL</div>
 </div>

The CSS:

#container{
  background-color:#0ff;
  float:left;
  width:500px;
  border-left:150px solid #0f0; » 
  /* The width and color of the left rail */
  border-right:200px solid #f00; » 
  /* The width and color of the right rail */
}
#leftRail{
  float:left;
  width:150px;
  margin-left:-150px;
  position:relative;
}
#center{
  float:left;
  width:500px;
  margin-right:-500px;
}
#rightRail{
  float:right;
  width:200px;
  margin-right:-200px;
  position:relative;
}

Check it out.

The center column has a right margin of -500px. This allows the left rail div to float all the way over it to the left edge of the center column. Negative margins pull the sidebars into place. There are a few ways to do this, but this one seems to work the best when we get to liquid layouts later on.

I’ve floated the container div to honor the column heights instead of setting overflow:hidden. This is because the sidebar divs are outside of the container div, floating over its borders, so they would be hidden using the overflow setting: IE does not hide them, but Firefox, correctly, does.

The columns don’t need a background color. Since the colors are set on the container div, which grows with the tallest column, the illusion of equal height is taken care of.

Liquid layouts

After figuring out the fixed-width layouts, I decided to attempt liquid layouts using the same technique. The sidebars would still need to be fixed-width since most browsers won’t respect a percentage-width border setting, but we can make the center column liquid.

The CSS:

#container{
  background-color:#0ff;
  overflow:hidden;
  margin:0 100px;
  padding-right:150px; /* The width of the rail */
}
* html #container{
  height:1%; /* So IE plays nice */
}
#content{
  background-color:#0ff;
  width:100%;
  border-right:150px solid #f00;
  margin-right:-150px;
  float:left;
}
#rail{
  background-color:#f00;
  width:150px;
  float:left;
  margin-right:-150px;
}

Take a look.

The markup is the same as the two-column, fixed-width version, with the exception of some additional content to show what happens when it wraps. The CSS doesn’t really change much either, but there are a few differences: the container two-column, fixed-width version no longer has a width, and I added height:1% so IE will respect overflow:hidden. (I used the star HTML hack for this, but could have used conditional comments to include an IE specific style sheet.)

I also added a margin to create space on the sides; this is optional. The padding-right shrinks the space the content div will take up: since the content div now has a width of 100%, if there was no padding on the container, the sidebar div would be outside of the container and hidden. Finally, the content div’s width switches from fixed width to 100%, and margin-right:-150px is added to the sidebar div.

As with the two-column, fixed-width layout, the left-rail version requires only minor changes to the CSS. I added a simple header and footer for grins, and you can view the source to see how it’s done.

Three-column liquid layouts

A three-column liquid layout with equal-height columns has been called many things: “Holy Grail,” “One True Layout,” “pain in the @$$”... The following technique is relatively painless, uses proper source order, does not require the use of an image, and seems to be bug-free.

The HTML:

 <div id="container">
   <div id="center">Center Column Content</div>
   <div id="leftRail">Left <br />Sidebar</div>
   <div id="rightRail">Right Sidebar</div>
 </div>

The CSS:

body{
  margin:0 100px;
  padding:0 200px 0 150px;
}
#container{
  background-color:#0ff;
  float:left;
  width:100%; 
  border-left:150px solid #0f0;
  border-right:200px solid #f00;
  margin-left:-150px;
  margin-right:-200px;
  display:inline; /* So IE plays nice */
}
#leftRail{
  float:left;
  width:150px;
  margin-left:-150px;
  position:relative;
}
#center{
  float:left;
  width:100%;
  margin-right:-100%;
}
#rightRail{
  float:right;
  width:200px;
  margin-right:-200px;
  position:relative;
}

The margin and padding go on the body this time. The margin pushes the layout in from the edges of the screen, and the padding is the width of the sidebars. The remaining area is the width that the container can grow to be: 100% of the browser width minus margin and padding. On the container div, I set borders and negative margins the same width as those borders. This places the borders over the body padding, making everything fall into place. Then we just can position the divs.

The next example illustrates a nested liquid two-column layout and a basic header and footer. View source to see how easy it is to put the content into place and style it. The nested two-column layout uses the border on the container technique. This allowed me to add a 2px left border to the content and a 2px right border to the rail and overlap them, creating the full-height divider between. This divider will grow with whichever column is tallest.

If you wanted to get carried away, you could eliminate the column divs and just position the pieces of content into place. Because the sidebars are really the borders of the container div, they will still work. If you really wanted to go crazy with it, you could eliminate the container entirely and put the borders right on the body, achieving a three-column liquid layout with columns of equal height and no container divs! The only problem is that the background color of the center column cannot be a different color from the rest of the body—and eliminating the containers makes it much more difficult to position and style things…but it can be done.

I should note again that these techniques only work with fixed-width sidebars, as only Opera lets you set a border width using percentages. Also, the rails cannot have an image for a background, but that may change with the border-image property in CSS3.

So there you have it: multiple columns, equal column heights, fixed or liquid center column, clean markup, and CSS. All it took was a little thinking outside the box.

Check it out.

About the Author

140 Reader Comments

Load Comments