CSS Frames v2, full-height

Way back in August of 2003 I wrote a short article called CSS Frames, in which I described a technique to emulate the visual appearance of HTML frames with CSS. That is more than three years ago, so I think it's about time to revisit the technique and improve it a bit, especially as the original CSS Frames demo page has been receiving loads of traffic lately.

I only had to take a quick look at the code to tell that it's been a few years since I made the demo. Looking at old code can be a bit embarrassing sometimes. The demo contains a list of browsers that I had tested the technique in. "Mozilla Firebird 0.61" is mentioned as one of them. Like I said, it was a while ago. So here is an update.

The reason for coming up with the CSS Frames technique was, of course, to avoid having to use HTML frames. If you don't know why you would want to avoid using frames, read my article Who framed the web: Frames and usability.

Now, on to the updated CSS Frames technique.

The layout: header, full-height content, footer

The layout I describe here will have a fixed header at the top of the page, a scrolling content area in the middle, and a fixed footer at the bottom of the page. The concept can be adapted to other types of frame-like layouts, but I'm leaving that as an exercise for you.

I have also made sure that the content area will stretch to 100 percent of the viewport height even if there isn't enough content to cause scrolling. For that I used the technique described by Peter-Paul Koch in 100% height.

The layout consists of three main blocks: #header, #content, and #footer. The frame effect is created in two steps. First, #header and #footer are given fixed positions at the top and bottom of the viewport respectively. Second, #content is given padding-top and padding-bottom to match the heights of #header and #footer.

All three blocks are contained in a div element with the id #wrap to enable easier control of the layout width and horizontal centering. #content is contained in #content-wrap because of the 100 percent height technique I'm using. The block that scrolls needs to have a height of 100 %, but it also needs padding-top and padding-bottom. That would add up to more than 100 %, so the padding is set on #content instead. Both have the same background.

Different widths

Depending on whether you want the layout to stretch across the entire width of the viewport or not, you will need to use different widths for the #header and #footer elements. Elements that have position:fixed are positioned with respect to the viewport, so just setting their widths to 100% (you need to set a width or they will shrinkwrap to fit their content) and adjusting the width of #wrap will not work. #header and #footer would still be as wide as the viewport, though unless left or right is specified their left edges would be at the left edge of #wrap, where they would be if they hadn't been positioned at all.

I've prepared two example documents to show the difference. In Example 1, the layout is fully fluid, so #header and #footer are 100 % wide. In Example 2 the layout is 40 ems wide, and the widths of both #header and #footer are set to that same value.


Here is the CSS that is used for Example 1:

  1. html,
  2. body {
  3. margin:0;
  4. padding:0;
  5. height:100%; /* 100 % height */
  6. }
  7. html>body #wrap {height:100%;} /* 100 % height */
  8. #header {
  9. width:100%;
  10. height:5em;
  11. }
  12. html>body #header {
  13. position:fixed;
  14. z-index:10; /* Prevent certain problems with form controls */
  15. }
  16. html>body #content-wrap {height:100%;} /* 100 % height */
  17. html>body #content {padding:6em 1em;} /* 6em = height of #header and #footer + 1em, 1em = give the content some breathing space */
  18. #footer {
  19. width:100%;
  20. height:5em;
  21. }
  22. html>body #footer {
  23. position:fixed;
  24. bottom:0;
  25. z-index:10; /* Prevent certain problems with form controls */
  26. }

And this is what makes Example 2 work, with the differences emphasised:

  1. html,
  2. body {
  3. margin:0;
  4. padding:0;
  5. height:100%; /* 100 % height */
  6. }
  7. html>body #wrap {height:100%;} /* 100 % height */
  8. #wrap {
  9. width:40em;
  10. margin:0 auto;
  11. }
  12. #header {
  13. width:40em;
  14. height:5em;
  15. }
  16. html>body #header {
  17. position:fixed;
  18. z-index:10; /* Prevent certain problems with form controls */
  19. }
  20. html>body #content-wrap {height:100%;} /* 100 % height */
  21. html>body #content {padding:6em 1em;} /* 6em = height of #header and #footer + 1em, 1em = give the content some breathing space */
  22. #footer {
  23. width:40em;
  24. height:5em;
  25. }
  26. html>body #footer {
  27. position:fixed;
  28. bottom:0;
  29. z-index:10; /* Prevent certain problems with form controls */
  30. }

The IE workarounds

Unfortunately, Internet Explorer for Windows up to and including version 6 does not support position:fixed. Since it is used by too many people to be ignored, a workaround is needed. Amazingly, IE7 does not need any workarounds. It just works! IE5 and 6, however, are sent a few extra rules using conditional comments:

  1. <!--[if lt IE 7]>
  2. <link rel="stylesheet" href="ie.css" type="text/css">
  3. <![endif]-->

The file ie.css contains the following CSS:

  1. html,
  2. body {background:url(foo) fixed;}
  3. #header,
  4. #footer {
  5. position:absolute;
  6. z-index:10;
  7. }
  8. #header {top:expression(eval(document.compatMode && document.compatMode=='CSS1Compat') ? documentElement.scrollTop : document.body.scrollTop)}
  9. #wrap,
  10. #content-wrap {height:100%;}
  11. #content {padding:6em 1em;}
  12. #footer {top:expression(eval(document.compatMode && document.compatMode=='CSS1Compat') ? documentElement.scrollTop +(documentElement.clientHeight-this.clientHeight) : document.body.scrollTop +(document.body.clientHeight-this.clientHeight));}

This works in IE 5, 5.5, and 6. (Though I didn't bother to fix the horizontal centering in IE 5.*. It's time to let go of IE 5.* anyway.)

Scrolling is slightly choppy, but it works. Since expressions rely on JavaScript being enabled this will not work when JavaScript is off. Fortunately all that happens in those cases is that the whole page scrolls. No content is hidden.

Progressive enhancement or graceful degradation?

In browsers that do not support position:fixed – with the exception of IE5-6 – the whole page will scroll, which is a perfectly acceptable fallback. I'm using a child selector to let only the browsers that support it see the position:fixed declarations. Whether you let browsers that do not support fixed positioning (with the exception of IE5-6/Win) see any CSS at all is a different matter.

This technique works in all modern browsers I have been able to lay my hands on, with one exception. The technique used to make the content area stretch to 100 percent of the viewport height causes some strange behaviour in iCab and IE/Mac. The problems are cosmetic and not too bad, so I think most will be able to live with that.

Other than that, I'm not aware of anything. That doesn't mean there aren't any problems however, so please speak up if you find any bugs or have suggestions for further improvement.

A note on scrolling

There are a couple of scrolling-related issues with this technique:

  • When you scroll by pressing the space bar or page up/down keys on your keyboard, this technique has a problem in that the document area will scroll too far, hiding part of the unread content behind the header or footer, depending on which direction you’re scrolling in.
  • The vertical scrollbar extends above and below the actual area that scrolls, which can feel odd.

For possible solutions to these problems, please see The all new fixed layout by Stu Nicholls.

Update (2006-09-05): Well, that didn't take long. As mentioned in comment #6, both examples suffer from what looks like a variation of the "jump on hover bug" described in Quirky Percentages in IE6's Visual Formatting Model in Internet Explorer 6. I currently have no solution to the problem, so if you know how to fix it, please post a comment with the solution :-).

The bug is triggered because the links change border-style on :hover. I commented that part out of Example 1, so the bug is not triggered there. I left it in Example 2 so you can see the exotic effect of the page shrinking ;-P.

Update (2006-09-05): I've managed to track the bug to the width:100% I use on the body element to prevent disappearing scrollbars in IE6 when the window is narrower than any part of the content with a set width.

I removed the width declaration from ie6.css for now, so no more funky jumping when you hover over the links.

Update (2006-09-16): Thanks to the excellent info provided by Georg in comment #36, I've managed to get around the problems in IE6 (I hope). As a bonus, the technique now also works in IE 5.*.


This article has been translated into the following languages:

Posted on September 5, 2006 in CSS