Equal Height Column Layouts With Borders And Negative Margins In CSS
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
:
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
withheight: 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 ofbody
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
andzoom
on#header
and#footer
make these elements new block formatting contexts.- For
#main
we usefloat
rather thanoverflow
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="https://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.
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 useinline-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="https://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.
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="https://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;
}
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="https://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.
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 need #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="https://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: relative9;
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 frombody
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
- Faux columns
- Ultimate multi-column liquid layouts
- Doug Neiner Method
- Nicolas Gallagher Method
- Balance your CSS Columns with JavaScript
- Absolute Columns
- Companion columns
Related Reading on SmashingMag:
- The Definitive Guide to Using Negative Margins
- Flexbox Is As Easy As Pie – Designing CSS Layouts
- CSS Float Theory: Things You Should Know