TJKDesign: Home Page

ez-css Putting the 'less' in table-less layouts. css-101 logo
Bookmark this article at these sites:

A CSS layout that does not rely on DIV, FLOAT, CLEAR nor structural HACK!

There are many CSS layouts out there. Some rely on AP (Absolutely Positioned) elements, others use FLOATs. The former method is considered bad practice for its lack of flexibility while the latter is a powerful solution in building robust layouts.

Unfortunately, like most powerful tools, FLOATs can be a dangerous method to employ or at least very frustrating. First of all, the FLOAT concept itself is not easy to properly understand, and second of all, FLOATs are a source of many browser bugs (mainly IE bugs) which make FLOAT constructs difficult to master across browsers... and easy to break.

This article demonstrates an original solution that addresses semantics, construct, and design issues to deliver robust layouts.

This is a sneak peek.

Starting with the usual markup (div based)

step 1

<div id="wp">
 <div id="hd">Header</div>
  <div id="bd">Body
   <div id="doc">
    <div id="s1">Section 1</div>
    <div id="s2">Section 2</div>
    <div id="s3">Section 3</div>
 <div id="ft">Footer</div>

Making it more semantic

DIVs are meaningless and cannot represent the structure of a document, but IMO lists create true semantic layouts. Because lists, unlike DIVs, translate the hierarchy/relationship that exists between items (the divisions/sections). However, here's what I really think about this.

step 2

<ol id="wp">
 <li id="hd">Header</li>
 <li id="bd">Body
  <ol id="doc">
   <li id="s1">Section 1</li>
   <li id="s2">Section 2</li>
   <li id="s3">Section 3</li>
 <li id="ft">Footer</li>

Using CSS properties

step 3

In "good browsers", containers are styled as table cells.

First, we remove from the markup the text that precedes the nested OL ("Body"). Then, we set width and auto margin to center the layout and we use "list-style:none" to make sure no browser shows a bullet near the sections. Then we style the LI that contains the UL with the three items (the columns) as a table. And finally we style that OL as a table row and each one of the LIs it contains as a table cell.

<style type="text/css">
  * {margin:0;padding:0;}
  #wp {width:55em;margin:0 auto;list-style-type:none;}
  #bd {display:table;}
  #doc {display:table-row;}
  #s1,#s2,#s3 {display:table-cell;}

Note that without list-style-type:none numbers would show up next to the LIs in IE Mac and Safari (display:inline takes care of IE Win and display:table-cell takes care of Gecko based browsers).

Taking care of IE using other CSS properties

step 4

In Internet Explorer, the LIs are styled as "inline-blocks".

For IE Win, we use display:inline;zoom:1;
IE Win does not do "inline-block" on block-level elements, so the trick is to use display:inline + zoom:1 instead. Note that if we use zoom it is because we need these elements to have layout (read on having Layout), but we cannot rely on the width declarations since we are styling the LIs as inline elements (which turns the width "trigger" off).
For IE Mac, we use display:inline-block;float:left; and we clear the columns using the footer.
Yes, I had to break my promise a bit: just to support IE Mac I do use float here.

Now we need extra declarations:

  • vertical-align to make the content of the LIs appear near the header rather than the footer (by default, content appears bottom-aligned).
  • width to make the LIs sit next to each other rather than take the whole width of their parent container (note that the widths add up: 12em + 29em + 14em = 55em).
  #s1 {width:12em;}
  #s2 {width:29em;}
  #s3 {width:14em;}
  #s1,#s2,#s3 {display:inline-block;float:left;}
  #ft {clear:left;}

The rules above use CSS hacks to serve different rules to different browsers:

  • Internet Explorer (IE 7 and IE Mac included) will not ignore a rule in which a comma precedes a declaration block (i.e. selector,{property:value;}).
  • The IE5/Mac Band Pass Filter is used to make sure only IE Mac sees these rules.

Because of IE 5.1 lack of math skills, we need to reduce the width set for the first column. So to take care of IE lt 5.5 we use a Conditional Comment:

<!--[if lt IE 5.5000]>
<style type="text/css">
/* to avoid columns #3 dropping in IE lt 5.5 */
#s1 {width:11.9em;}

Keep in mind this fix for IE lt 6 (in case your last column drops).

Adding borders

step 5

Adding borders for good browsers is pretty straightforward.

  #hd {border-bottom:1px dotted #555;}
  #s1 {border-right:1px dotted #555;}
  #s3 {border-left:1px dotted #555;}
  #ft {border-top:1px dotted #555;}

The border on the columns would make the last column drop in IE (Win and Mac) so we need to zero out these two declarations for IE (using the "*property" hack):

#s1,#s3 {*border:0;}

Adding borders for IE

step 6

Because border and width adds up (see box model), this styling is making columns drop in IE 6 and 7. We could cheat with the value of the main container to avoid this, but there is another problem anyway: IE makes the columns only as high as their content so borders would not be painted from top to bottom.

To address this issue, we style elements that we know have the same height as the tallest column. These are the second LI and its nested OL, two semantic "wrappers". As a side note, we could add background colors as well (read Full length column background colours). Note that background and border for IE in the "fluid center" layout on the demo page are created using a totally different method, this one based on structural hacks (read How to Make Equal Columns in CSS).

The width of the LI is made equal to the width of the two first "columns" while the width of the OL is made equal to the width of the first "column". Then we position the two last "columns" outside of their containers using negatrive values (using the left and margin properties). The section about creating a fluid layout explains this in more detail.

  /* Hides from IE-mac \*/
  #bd,#doc,{border-right:1px dotted #555;}
  /* End hide from IE-mac */

Note that this time we are using a different CSS filter to make sure IE Mac does not see the above rules. As a side note, the demo page will show that the layout is broken in IE 5 since this browser does not wrap the comments we have inside the pre elements, but the next step will take care of this issue.

Preventing breakage

Step 7

To keep the behavior of this layout consistent across browsers we need to do a few things.

First of all, we need to make sure "good browsers" do not reflow the columns depending on content, for that we use:

  #bd {table-layout:fixed;}

If content is larger than its container, the container will expand, breaking the layout in IE lt 7. To prevent this, we break long strings (this can be seen in the last demo page) and cut off content on the horizontal axis:

#bd {_word-wrap:break-word;}
#s1,#s2,#s3 {_overflow-x:hidden;}

The underscore property hack (_property:value) is used to serve the two rules above to IE 6 and below. Note that these rules have fixed the issue we had with long strings in IE 5 (overflow-x takes care of IE lt 5.5 and word-wrap takes care of IE 5.5).

Adding headings, some content and padding

step 8

We add vertical padding to the sections, but because of the box model, we create left/right padding through the elements they contain.

  #hd,#s1,#s2,#s3,#ft {padding:15px 0;}
  #hd *,#s1 *,#s2 *,#s3 *,#ft * {padding:15px 15px 0 15px;}

This is just a quick and dirty way to style this layout, you should use your favorite CSS reset method with the appropriate rules to style nested elements like UL, OL, DD, etc. If you do not care about DIVitis, then use a DIV inside each section and add margin or padding to it.

From "elastic" (EM unit) to fluid (%)

Float-less Fluid Layout

We need to do some serious math to change the width of the elements as in Internet Explorer parent containers are styled with smaller values (remember the border trick?) and calculation is done based on the width of these parents. So...

First, we set the width of these two elements:

  #wp,#bd {width:100%;}

Note that because we are using table-layout:fixed it is important to give "bd" an explicit width (to prevent the browser from expanding the width of columns depending on their content).

Then, we make sure all widths add up (100% = 20% + 60% + 20%):

  #s1 {width:20%;}
  #s2 {width:60%;}
  #s3 {width:20%;}

In IE Win, we are positioning the elements, so we need this:


The width of "bd" equals "s1" + "s2":


The width of "doc" must be the same as "s1" (20% = width of the parent container * 25%):


The width of "s1" is set to 100%, so it will match the width of its parent container ("doc"):


The width of "s2" is set to 300% which will make it 3 times bigger than its parent container ("doc"), we also set a negative margin equal to that same value so "s2" escapes its parent (sitting to the right edge of "doc"):


The width of "s3" is set to 100% which makes it as wide as "doc" (as "s1"):


For "s3", we set negative top and margin values that are equal to the width of "s2":


So we wrap all the above inside a "comment" hack (as the one used earlier) to hide all this stuff from IE Mac:

#wp,#bd {width:100%;*margin: 0 -3px;}
/* all widths add up (100% = 20% + 60% + 20%) */
#s1 {width:20%;}
#s2 {width:60%;}
#s3 {width:20%;}
/* IE Win only: trick to create the background columns or borders */
/* Hides from IE-mac \*/
/* end hides from IE-mac */

Setting min-width and max-width

Floatless Fluid Layout with min/max width

To prevent this fluid layout becoming too narrow or too wide, we set min/max widths:

For modern browsers, this is pretty straightforward:

#wp {min-width:820px;max-width:1200px;}

For IE lt 7, I prefer to use the setExpression and recalc methods rather than CSS expressions. This is something I recently discovered and it seems much less evil than CSS expressions.

<!--[if lt IE 7]>
<script type="text/javascript" defer="defer">
function ieMinMax(){
if(document.compatMode && document.compatMode=='CSS1Compat'){
document.getElementById("wp").style.setExpression("width","documentElement.clientWidth < 800 ? '800px' : documentElement.clientWidth > 1200? '1200px' : '100%'");
document.getElementById("wp").style.setExpression("width","document.body.clientWidth < 800 ? '800px' : document.body.clientWidth > 1200? '1200px' : '100%'");

How to load the demo page styled with a specific layout

It is possible to switch between styles without using the links in section 1 ("left side bar"), to do this, simply use the query string to call the layout you want (helpful if you wish to check these layouts through a service like Browsercam).

Linearized, each section is as wide as the viewport's width:
Fixed width in pixels: 220,580:
Elastic width (in em): 20,35:
Fixed width in pixels: 210,400,190:
Elastic width (in em): 12,29,14:
Fluid width in %: 20,60,20 with min/max (800px/1200px):
Fluid width in %: 20,60,20:
Fluid center in modern browsers (all widths set in "%" in IE 5 and 6):

Things to consider

  • If you're not planning to support IE Mac, there are many rules and filters you may want to remove.
  • If you're not planning to support IE Win lt 5.5, you may ignore the Conditional Comment used to set a smaller width for the first column.
  • If CSS validation is important to you, regroup all IE rules and put them inside a Conditional Comment.
  • If you are looking for a 3 column layout with fluid center, stay away from this solution because (as far as I know) it can't be done in IE lt 7.
  • If you are use to working in Dreamweaver's design view and these layouts do not make any sense, then look in the help files for "design-time styles sheet" and use the following:
    #s1,#s2,#s3 {float:left;overflow:hidden;border:0;}
    #ft {clear:left;}

    Make sure to not publish these rules. Their only purpose is to help Dreamweaver render columns next to each other rather than under each other while keeping the footer at the bottom. Obviously, this won't give you a "WYSIWYG" picture (i.e. you'll lose the borders), but it should help you work in Dreamweaver design view with no problem.

Should we switch to using lists instead of DIVs?

Because of the way screen readers treat Lists, I recommend against using this construct. I have used OLs here merely as a proof of concept (and to make the title of this article a bit more interesting). However, regarding semantics, I must say that the few discussions I recently participated in have not convinced me that this is complete nonsense :-)

Please use this contact form to send feedback and report errors.