Smashing-magazine-advertisement in Equal Height Column Layouts with Borders and Negative Margins in CSSSpacer in Equal Height Column Layouts with Borders and Negative Margins in CSS
 in Equal Height Column Layouts with Borders and Negative Margins in CSS  in Equal Height Column Layouts with Borders and Negative Margins in CSS  in Equal Height Column Layouts with Borders and Negative Margins in CSS

“What? Another “Equal Height Columns” article? Enough already!” If this is what you think, then think again because this solution is different. It does not rely on any of the usual tricks. It does not use images, nor extra markup, nor CSS3, nor pseudo-classes, nor Javascript, nor absolute positioning. All it uses is border and negative margin. Please note that this article will also demonstrate different construct techniques and will brush up on a few concepts.

1. Centering columns without a wrapper div

This layout will be built without a wrapper div:

Screenshot-1 in Equal Height Column Layouts with Borders and Negative Margins in CSS

The basics

We use the background of body and the border of one of the columns to create background colors that vertically fill the “row”.

The markup

<div id="header">
	<h2><a href="#">Header</a></h2>
	<p>Lorem ipsum...</p>
</div>

<div id="sidebar">
	<h2><a href="#">Sidebar</a></h2>
	<p>Lorem ipsum...</p>
</div>

<div id="main">
	<h2><a href="#">Main</a></h2>
	<p>Lorem ipsum...</p>
</div>

<div id="footer">
	<h2><a href="#">Footer</a></h2>
	<p>Lorem ipsum...</p>
</div>

Tip: always include links within your dummy text to spot stacking context issues.

About using body as a wrapper:

  • always style html with a background to prevent the background of body from extending beyond its boundaries and be painted across the viewport.
  • never style html with height: 100% or the background of body will be painted no taller than the viewport.

The CSS

html {
  background: #9c9965;
}

body {
  width: 80%;
  margin: 20px auto;
  background: #ffe3a6;
}

#header {
  background: #9c9965;
}

#sidebar {
  float: left;
  width: 200px;
  background: #d4c37b;
}

#main {
  border-left: 200px solid #d4c37b;
}

#footer {
  clear: left;
  background: #9c9965;
}

What do these rules do exactly?

  • We style html with a background to prevent the browser from painting the background color of body outside our layout.
  • We style body with auto margin to center the layout; the width is set using percentage. The background declaration is for #main.
  • We style the background of #header to mask the background color of body (the same goes for #footer).
  • The background color of #sidebar matches the border color of #main. This is the trick we use to make our columns appear as being of equal height.
  • The footer clears any previous float so it stays below the columns, at the bottom of the layout.

If you take a look at this first step, you’ll notice that the heading in #sidebar is not vertically aligned with the heading in #main and that we have some gap at the top and bottom of the #sidebar. This is because out of these two containers, only one is a block formatting context. So margins do not collapse in #sidebar while they do in #main. This also means that #main will not contain floats and that applying clear:left to any nested element in there will clear #sidebar as well.

So to prevent any float and margin collapsing issues we make all the main boxes block formatting contexts.

#header,
#footer {
  overflow: hidden;
  zoom: 1;
}

#main {
  float: left;
}

#sidebar {
  margin-right: -200px;
}

Note: if things look a bit different in IE 6 and 7 it is because in these browsers default margins do collapse inside block-formatting contexts. The following should also be considered:

  • overflow and zoom on #header and #footer make these elements new block formatting contexts.
  • For #main we use float rather than overflow to prevent potential issues if we had to offset some nested content.
  • The negative margin keeps #main into place because now that it is a float, its border box comes next to the margin box of #sidebar (the negative vlaue must match the dimension of the said box).

If you look at the second step, you’ll see that the border of #main hides #sidebar. This is because of the stacking context. In the flow (tree order), #main comes after #sidebar so the former overlaps the latter.

Positioning #sidebar brings it up in the stack.

#sidebar {
  position: relative;
}

Note: if you make #main a new containing block you’ll revert to the original stack order. In this case, you’ll need to use z-index to keep #sidebar on top of #main.

If you look at step three, you’ll see that we are almost there. The last things to do are mostly cosmetic. I’ve inserted a base styles sheet:

<link rel="stylesheet" type="text/css" href="http://tjkdesign.com/ez-css/css/base.css">

and then added these rules:

html {
  height: auto;
}

body {
  border: 1px solid #efefef;
}

#header,
#main,
#sidebar,
#footer {
  padding-bottom: 2em;
}

Why do we need these rules?

  • We can reset the height on html so the background of #main is not cut-off at the fold (this styling is inherited from the base styles sheet).
  • We can draw a border all around the layout.
  • Because the base styles sheet only sets top margins, we can create gaps at the bottom of the main boxes via padding.

Note: The rule for html is shown here, but it makes more sense to remove that rule from the base styles sheet rather than overwriting the declaration here.

This is the last step for this first layout. It relies on these simple rules:

html {
  height: auto;
  background: #45473f;
}

body {
  width: 80%;
  margin: 20px auto;
  background: #ffe3a6;
  border: 1px solid #efefef;
}

#sidebar {
  float: left;
  position: relative;
  width: 200px;
  margin-right: -200px;
  background: #d4c37b; /* color must match #main's left border */
}

#main {
  float: left;
  border-left: 200px solid #d4c37b; /* color must match #sidebar's background */
}

#header,
#footer {
  clear: left;
  overflow: hidden;
  zoom: 1;
  background: #9c9965;
}

#header,
#main,
#sidebar,
#footer {
  padding-bottom:2em;
}

2. Creating a 2-col-layout with two borders in between the columns

We’ll build this one with a single wrapper for our two columns, and we want to paint a vertical border between the two columns.

Screenshot-2 in Equal Height Column Layouts with Borders and Negative Margins in CSS

The basics

The wrapper div allows us to be a bit more creative here. The background of the wrapper is used to paint the background of one column, while its left border is used to paint the background color of the other column. The vertical border will be done by overlapping the right border of the left column with the left border of the right column.

Note: if you have use a fixed width layout (vs. fluid like here), then the wrapper can be used to create the two background colors as well as the vertical border at the same time. This is done by using the left border for the left column, the right border for the right column and the background for the vertical border. Yes, this means the content box is one pixel wide and that negative margins are used to pull the columns into place.

Markup

<div id="header">
	<h2><a href="#">Header</a></h2>
	<p>Lorem ipsum...</p>
</div>

<div id="wrapper">
  <div id="sidebar">
  	<h2><a href="#">Sidebar</a></h2>
  	<p>Lorem ipsum...</p>
  </div>
  <div id="main">
  	<h2><a href="#">Main</a></h2>
  	<p>Lorem ipsum...</p>
  </div>
</div>

<div id="footer">
	<h2><a href="#">Footer</a></h2>
	<p>Lorem ipsum...</p>
</div>

CSS

We start with the generic rules from the previous demo:

html {
  background: #45473f;
}

body {
  width: 80%;
  margin: 20px auto;
  background: #ffe3a6;
}

#header,
#footer {
  overflow: hidden;
  zoom: 1;
  background: #9c9965;
}

#sidebar {
  float: left;
  width: 200px;
}

#main {
  float: left;
}

To which we add position: relative:

#wrapper {
  display: inline-block;
  border-left: 200px solid #d4c37b;
  position: relative;
}

#sidebar {
  margin-left: -200px;
  position: relative;
}

Note: there is no need to use clear on the footer since #wrapper contains both floats.

  • Rather than using overflow/zoom, we use inline-block to create a new block formatting context (this declaration also triggers hasLayout). The left border will paint a background color behind #sidebar.
  • Negative margin is used to bring #sidebar outside the content box of the parent’s container (to make it overlap the border of #wrapper).

The case of IE6: If the above rules use position: relative (twice), it is because of IE 6. It is applied on #wrapper to prevent #sidebar from being clipped outside of its content box. It is also applied on #sidebar to make sure that the elements are “always” painted with the proper offset.

If you look at this first step, you’ll see that we have everything working, but the vertical border is in between the columns. You should also notice that in browsers other than IE 6 and 7, there is a small gap at the bottom of #sidebar (at the bottom #wrapper actually). This is because #wrapper is styled with inline-block so it is sitting on the baseline of the line box. The gap you see is the “descender space” (the space reserved for descenders in lowercase letters).

So these are the rules to remove the gap and create the vertical border:

#wrapper {
  vertical-align: bottom;
}

#sidebar {
  margin-right: -1px;
  border-right: 1px solid #888;
}

#main {
  border-left:1px solid #888;
}

What do these rules do?

  • vertical-align: bottom makes #wrapper sit at the bottom of the line box rather than the baseline.
  • the two borders (for #sidebar and #main) overlap because of the negative right margin set on #sidebar. This overlap guarantees that this “common” border will be as tall as the tallest column.

If you look at step two, things look much better. The last things to do is to add the base styles sheet and the same rules we used at the end of the first demo:

<link rel="stylesheet" type="text/css" href="http://tjkdesign.com/ez-css/css/base.css">

and then add these rules:

html {
  height: auto;
}

body {
  border: 1px solid #efefef;
}

#header,
#main,
#sidebar,
#footer {
  padding-bottom: 2em;
}

This last demo for this layout includes the above rules.

3. Creating a three column layout with a border in between the columns

We’ll build a layout with a single #main-wrapper, one containing all the divs. This approach complicates things a bit, but it also allows to tackle new challenges. Please note that with this layout, the vertical borders will not show in IE 6 and 7.

Screenshot-3 in Equal Height Column Layouts with Borders and Negative Margins in CSS

The basics

We use the wrapper to create the background of the three columns. The left and right borders of the wrapper will be used for the two side bars while its background will be used for the main content.

The markup

<div id="wrapper">
  <div id="header"><h2><a href="#">Header</a></h2><p>Lorem ipsum...</p></div>
  <div id="sidebar"><h2><a href="#">Sidebar</a></h2><p>Lorem ipsum...</p></div>
  <div id="aside"><h2><a href="#">Aside</a></h2><p>Lorem ipsum...</p></div>
  <div id="main"><h2><a href="#">Main</a></h2><p>Lorem ipsum...</p></div>
  <div id="footer"><h2><a href="#">Footer </a></h2><p>Lorem ipsum...</p></div>
</div>

CSS

We start with the generic rules from the previous demos:

html {
  background: #45473f;
}

body {
  width: 80%;
  margin: 20px auto;
  background: #ffe3a6;
}

#header,
#footer {
  overflow: hidden;
  zoom: 1;
  background: #9c9965;
}

#sidebar {
  float: left;
  width: 200px;
}

#main {
  float: left;
}

To which we add:

#wrapper {
  border-left: 200px solid #D4C37B;
  background-color: #ffe3a6;
  border-right: 200px solid #D4C37B;
}

This code sets the background color for the three columns. In the same sequence as the above declarations.

If you look at this first step, you’ll see that we have achieved the background effect we are looking for, but things look pretty broken. Everything shows inside the wrapper’s content box.

These next rules should fix the display of the three columns (zoom: 1 for the #wrapper and position: relative for #sidebar and #aside):

#wrapper {
  zoom: 1;
}

#sidebar {
  margin-left:-200px;
  position: relative;
}

#aside {
  float: right;
  width: 200px;
  margin-right: -200px;
  position: relative;
}

#aside is given a width and floated to the right. The negative margins pull each side bar over the wrapper’s border — outside of the content box.

Note:IE 6 and 7 needs #wrapper to have a layout, hence the use of zoom. IE 6 needs the two position declarations for the same reason as in the previous demos.

If you look at step two, you’ll see that #header does not stretch across the entire layout and that #footer is nowhere to be found.

These two rules should take care of everything:

#header,
#footer {
	margin-left: -200px;
	margin-right: -200px;
	position: relative;
}

#footer {
  clear: both;
}

The negative margin on both sides of #header and #footer stretches the two boxes outside of the wrapper’s content box. clear:both makes the footer clears all the columns. This is step three.

Once again, the position declaration is for IE 6. Just remember to always position elements that you offset.

What’s next?

You know the drill. We insert a base styles sheet in the document:

<link rel="stylesheet" type="text/css" href="http://tjkdesign.com/ez-css/css/base.css">

and add the usual:

html {
  height: auto;
}

body {
  border: 1px solid #efefef;
}

#header,
#main,
#sidebar,
#footer {
  padding-bottom: 2em;
}

Step four shows how things look before we tackle the vertical borders.

Adding vertical borders

The following technique is inspired from the companion columns technique (Ingo Chao) and the Nicolas Gallagher method.

To get the effect we want (two borders touching each other), we use generated content to which we apply a background color and a border.

The CSS

html:before {
	content: ".";
	position: absolute;
	height: 20px;
	background: #45473f;
	left: 0;
	right: 0;
	z-index: 2;
}

body {
  border-top: 0;
}

#header {
  border-top: 1px solid #fff;
}

#wrapper {
  position: relative;
}

#header,
#footer {
  z-index: 1;
}

#wrapper:before,
#wrapper:after {
  content: ".";
  position: absolute;
  width: 1px;
  height: 2000px;
  background: #9c9965;
  bottom: 0;
}

#wrapper:before {
  left: 0;
  border-left: 1px solid #fff;
}

#wrapper:after {
  right: 0;
  border-right: 1px solid #fff;
}

body {
	position: relative\9;
	z-index: -1;
}

OK, so what’s going on here?

  • The fake borders get out of the container (at the top), so the first rule paints generated content on top of them. Note that we would not need this rule if the color of the fake borders was the same as the page’s background (html), or if there was no gap between the top of the viewport and the layout.
  • Because these borders are painted over the border around body, we move the top border from body to #header.
  • To properly position the fake borders, we need to make the wrapper the containing block for the generated content.
  • We bring #header and #footer above the stack so they hide the fake borders which are painted inside the wrapper (from bottom to top).
  • This is the generated content we use to create the columns.

The case of IE 8: The last rule is for IE 8. Without this, IE 8 would not paint the generated content over the borders that escape the wrapper (at the top). If this declaration is sandboxed via the “\9″ hack, it is because Gecko browsers would make everything unclickable/unselectable.

Note: these pseudo-classes are not supported by IE 6 and 7, so in these browsers, there are no borders between the columns.

Things to consider

The third layout uses one main wrapper, but it would make more sense to use a inner wrapper instead to hold only the columns. In case this route was taken here, then it was only for those of you who are stuck with this type of construct, but want to implement this solution for equal height columns.

When absolutely positioning elements inside a containing block with wide columns like in the last two demos, remember that the reference is the padding box, so 0 for right or left may not be the value you would want to use.

Further reading

(ik) (vf)


© Thierry Koblentz for Smashing Magazine, 2010. | Permalink | Post a comment | Add to del.icio.us | Digg this | Stumble on StumbleUpon! | Tweet it! | Submit to Reddit | Forum Smashing Magazine
Post tags: , , ,