min-height: fixed;

September 16, 2004 10AM PST

After one too many times wistfully wishing I could scale fixed-size elements according to their content in a cross-browser friendly way, I did something about it. Presenting min-height, without the min-height.

If I had a penny for every time I’ve run into a situation where I really needed a block of content with a specific height, but decided not to due to the issues involved… well, I’d have a few bucks anyway.

There’s no reliable way to place elements inside a parent and then have it expand gracefully along with the elements, if the parent has a starting height value. This is precisely the problem min-height solves, but it’s frustrating that it’s so unusable.

The Test Case

Take this demo page, for example. Placing the navigation where it is requires a header of an exact height, otherwise the proportions matching the photo and the rest of the header are completely off.

Example site

But if the list of nav items grows, or the user has a larger font size, or anything at all happens that would require the nav’s height to expand, it expands outside of the header and overlaps into the content area below within browsers that adhere to the spec for height property.

Example site with overlapping header

The Problem

min-height is the logical choice for finding a solution that would allow the header to expand gracefully as the nav does, but it isn’t supported by Internet Explorer or, unfortunately, Safari.

I say unfortunately because there’s a fantastically easy way around the issue in IE, but not Safari — height is basically treated as min-height. With a bit of filtering, it’s a snap to craft a solution that works between IE and Mozilla. But once you throw Safari in the mix, it’s not that simple.

Since min-height itself is unusable, there have only been a few possible ways of dealing with situations like this. Take this example page with four stacked, variable-sized divs. The content itself dictates the height of each, which may be fine for most purposes. But like the graphical example above, sometimes it makes more sense visually for the content to fit within a certain-sized area.

Let’s say the divs should be a minimum of 200px high. If we apply a height attribute directly to each, well, the results aren’t quite what we’d hope — scroll to the third & fourth divs to see them overlapping.

The problem is evident in anything but IE; the results are in fact exactly what we’re hoping for in IE, just not actually correct according to the spec. When we’re also trying to account for dynamic content (which could be any length) or scalable text (which could be any size), it’s even harder to predict a box’s optimal height. Defining specific heights will lead to overlap sooner or later, and that’s no good.

Potential Solutions

We could try overflow: auto; and force scrollbars to avoid it in all situations, but that’s a really kludgy workaround and no one likes scrollbars anyway. It’s a technical fix to a technical problem that affects the design adversely. No good.

We might be able to script our way around the problem (or use a proprietary attribute like IE’s expression property), but for the sake of validation and not relying on an external script, what about a fix using valid CSS alone?

Well, that pretty much exhausts the range of possibility. Or so I always believed.

I keep running into this problem in my own work, and I’m always unsatisfied by the eventual results. Letting the content dictate minimum size messes up proportions unless everyone and their dog is using the same default font size, which they’re not, and it forces limitations on how much content you can place inside the block which, given the required flexibility of most web design tasks these days, is never a good idea.

I finally got fed up with this limitation and decided it was time to fix it once and for all. And by George I think that’s exactly what I’ve done.

Solving It

My method of attack involved figuring out how to duplicate 200px of min-height without actually using min-height. I could see only one real way of going about it — I needed to prop open the parent element with some sort of 200px high spacer that wouldn’t affect the internal layout (and thus allow the element to expand as the content does, but not collapse past 200px if the content is short).

So I tried a few things and spent a while thinking about it, and here’s what ended up working. Take this construct:

<div class="box">
 <p>Lorem ipsum dolor sit amet.</p>
</div>

Applying 200px of top padding to the parent div guaranteed it would always be at least 200px high — that’s half the battle right there.

.box {
 padding-top: 200px;
}

The child p element is pushed down below that initial gap, so moving it back up to the top of the parent became the new challenge. Relative positioning would work, but it would also leave a space for the element. The total height of the construct would be child height + 200px, no matter what size the child was. No good.

Instead, a negative top margin of 200px would effectively erase that initial lead, and allow the entire construct to size according to the child… except when the child was smaller than 200px. Then the containing parent’s bottom edge would determine the size of the construct, which would always be 200px, which is our desired min-height effect.

.box {
 padding-top: 200px;
}
.box p {
 margin-top: -200px;
}

In Safari and Firefox, we’ve got it solved, but not IE. Remember that IE already does min-height though, because that’s how it treats height, so a bit of browser-specific filtering should do it:

/* for Mozilla/Safari */
*>.box {
 padding-top: 200px;
}
*>.box p {
 margin-top: -200px;
}
/* for IE */
* html .box {
 height: 200px;
}

The problem is now IE5/Mac — it actually applies all three styles. It gets the first two right, but then it also has the same parsing bug that IE/Win does and applies the second one, which really makes a mess of things. So an extra filter for IE5/Mac is necessary:

/* for Mozilla/Safari */
*>.box {
 padding-top: 200px;
}
*>.box p {
 margin-top: -200px;
}
/* for IE, with IE5/Mac backslash filter \*/
* html .box {
 height: 200px;
}
/* end filter */

Is that it? Not quite yet, there’s also Opera. Unfortunately it doesn’t want to seem to keep our containing box open; it looks like the lack of an explicit height value collapses the entire parent element, padding included. We can keep it open and get our padding back by setting a min-height, though… any value will do:

/* for Mozilla/Safari/Opera */
*>.box {
 padding-top: 200px;
 min-height: 1px;
}
*>.box p {
 margin-top: -200px;
}
/* for IE, with IE5/Mac backslash filter \*/
* html .box {
 height: 200px;
}
/* end filter */

Final Notes

And there we have it. (Also, a fully-commented version) It’s a bit messy, but it works in everything, and presumably it applies equally to the min-width property (although I haven’t tested that yet). The only caveat seems to be Opera 6, which despite all that still collapses the parent. Oh well, Opera 6 is two years old now and Opera users ought to know better than that, so I’m not losing sleep; it degrades gracefully, anyway, and that’s what’s important.

The only real markup dependency is on a parent that contains a single child. The construct used in the example has a single p element; adding a second one would mess things up considerably though, so in cases where more elements are needed within the parent it’s a better idea to add a secondary wrapper div as the child.

Example site with fixed header

And our example from above? Piece of cake. When the navigation is longer, the content bumps down to account for the larger header. When it’s short, the header retains its minimum height.

Addendum: In theory, this should also work the same as the above method, but the syntax is a bit nicer. I’ll test it more thoroughly, so for now there are no guarantees about the utility of this code. (Thanks to Henrik Lied for pointing out !important, which I always manage to forget about)

.box {
 padding-top: 200px !important;
 height: auto !important;
 height: 200px;
 min-height: 1px;
}
*>.box p {
 margin-top: -200px;
}

12/2/04 Addendum: maybe not. Faruk Ates reports the second example doesn’t work in IE. However, Mitchell Stokely provides this further variation based on his Stokely hack which remains unverified and untested at the moment:

/*IE 6 and Safari Hack*/
/*read by Mozilla 1.0-1.4,IE6,Safari*/

html*.box{
[height:100%;/*necessary to hide from Mozilla*/
height:100%;/*read by Safari*/
]height:100%;/*only read by IE6*/
}
.dummyend[id]{clear: both;
/*end hack using dummy attribute selector for IE5 mac*/}