Cooking with Webstandards! Taste the full flavour of the Web

CSS - Auto-height and margin-collapsing

...or..miraculously shrinking containers!

Some of you may be aware of the fact that I am co-teaching a CSS-Workshop over at IWA/HWG. This week two students ran into exactly one and the same problem so that I had to find an explanation. So this entry is basically meant for them.

If you are working with CSS on a regular basis, you might have come across this behaviour. As did I. And if you are like me, you might have found a solution that works around it. But I have to admit that I never really understood what was happening.

So here is the first part of this problem. Consider the following markup:

<div>
   <p>This is a paragraph within a <code>div</code></p>
</div>

We'd have the following stylesheet:

div {
   background-color: #3C75AE;
   color: #fff;
   margin-top: 10px;
}
	
p {
   margin-top: 20px;
   margin-bottom: 20px;
   border: 1px solid #EB6B0E;
}

I am sure that by quickly having a look at the markup and the styles most of you would expect the following (even I did that):

But, surprisingly, in a modern browser it renders like that:

You can have see this in action by checking out Example 1.

What triggers this behaviour? There are two aspects coming into play here. One of them being the behaviour of margin-collapsing. This means that correctly implemented user agents collapse vertically adjacent margins. Using our example from above that is to say that the top border edge of our div is 20px away from a preceeding or its containing element (assuming that any eventual preceeding element hasn't a bottom margin that is larger than 20px, which isn't the case here. Let's also assume and state that our div is the first and only child of body.) and not 30 pixels. So the smaller of the two margins is eliminated in favour of the larger. This is illustrated in the following figure:

Fair enough, you'd probably say, but why isn't the paragraph 20 pixels away from the div's top outer border edge like shown above in figure 1? Why does the paragraph's margin "stick out" of the div?

Before answering this question I am going to provide you with a figure that shows you the complete box model for reference (okay, maybe not complete, I focused on the vertical axis, since that is all that matters here):

Now, the answer to our questions from above is derived from the fact how the height for a block-level element is calculated in CSS. If such an element has no height defined, it will be set to auto, which is the default height for block-level elements. Thus, in our example the height of the div is per definition the distance from the outer top border edge to the outer bottom border edge of the paragraph. Any vertical margin of the paragraph therefore will stick out of the div. The following two figures try to illustrate this. The first one shows the paragraph's box, while the second one outlines the beforementioned calculation of the div's height.

How would we then avoid the collapsing of the topmargins? I want the paragraph being 20 pixels away from the div's top border edge, how would I do that? The solution to this question lies in the question already. We have to change the height of the div by either adding a border or a padding. Let's add the following to our above styles:

div {
   background-color: #3C75AE;
   color: #fff;
   margin-top: 10px;
   padding-top: 1px;
   padding-bottom: 1px;
}

This little addition now completely changes how the height of the div (which is still auto) is calculated. Now its height will be the distance from the outer top margin edge to the outer bottom margin edge of the paragraph. The same would have been valid if we had added a top and bottom border instead of top and bottom padding. See the illustration below or check out Example 2.

Another example involving a floated element!

The following is a simplified reconstruction of what happened to some of my students in the workshop. The given markup would be something like this:

<body>
 <div id="links">
   A list of links
 </div>
 <div id="content">
   Some lines of anonymous text here....
 </div>
</body>

Furthermore, we apply the following styles:

body {
   font: 100%/1.5 Georgia, "Times New Roman", Times, serif;
}

div#links {
   float: right;
   width: 50%;
   margin-top: 0;
   background-color: #EB6B0E;
   color: #fff;
}

div#content {
   background-color: #3C75AE;
   color: #fff;
   margin-top: 30px;
   border: 1px solid #3C75AE;
}

Let me show you the rendering results of three different browsers first:

Mozilla rendering
Mozilla 1.7

Opera rendering
Opera 7.5

Internet Explorer rendering
Internet Explorer 6

You can check your own browser by looking at Example 3.

Well, that's pretty neat, isn't it? In fact, that is why we like CSS so much :)

But given our markup and our style declarations, Mozilla does it correct. Under certain circumstances (I'll explain that a bit later) Opera is correct either. Internet Explorer (surprise, surprise) is wrong here.

At first glance I thought to myself that Mozilla must be wrong. It seemed to clearly violate the float rule number 8, which states:

A floating box must be placed as high as possible.

So, Opera must be right, and even IE is coming close. This second thought alone should strike all alarm bells! Let us see what else is available. Take a look at float rule number 4:

A floating box's outer top may not be higher than the top of its containing block.

So it all comes down to one thing again. The containing block in our case would be body. Where is the top of body and what is the height of body? Take a look at our body styles. There is not much in there, just a font declaration. So the initial values for margin, padding and border will be assumed. Those are 0, 0 and none which means no margin, no padding, no border.

If we now consider what we have learned in our first example, the height of body will be the distance from the outer top border edge to the outer bottom border edge of its normal-flow block-level child (= the content div), while the margins of that child will again "stick out" of it.

But how can Opera render it completely different and nonetheless be correct? In this special case, you have to be aware of the fact that most browsers have their own user agent stylesheets implemented. These stylesheets rule the rendering of hyperlinks for example, or the font family and size that is displayed on completely unstyled pages. Additionally, they set an explicit value for margin and padding for body.

Since we have declared neither in our styles, our initial values will be outruled by the user agent's styles. Now the main point here is, that Opera has different styles than Mozilla and Internet Explorer. The Opera styles declare:

body {
   margin: 0;
   padding: 8px;
}

On the other hand, Mozilla and Internet Explorer assign the following styles:

body {
   margin: 8px;
   padding: 0;
}

This is only a slight difference, but it has a lot of impact on our example design. Let us copy Opera's styles and add the following to our working example:

body {
   font: 100%/1.5 Georgia, "Times New Roman", Times, serif;   
   margin: 0;
   padding: 8px;
}

Since there is now a padding, the height of body will change. It will be now the distance from the outer top margin edge to the outer bottom margin edge of its normal-flow block-level child.

This last example is also available. If you wish, please have a look at Example 4.

If the above illustrations are too small for your eyes, I have created a page containing all of them at a higher resolution.

Recommended reading

An article written by Andy Budd some time ago and a book from Eric Meyer. The latter did help me a lot in figuring out these things.

  1. Andy Budd: No margin for error
  2. Eric Meyer: Cascading Style Sheets, The Definitive Guide - Second Edition
  3. CSS 2 Specifications: 10.6 Computing heights and margins

Posted by Minz Meyer at July 28, 2004, 03:46 PM | To Top

Other ingredients

The joys of margin-collapsing :-)

In most case(s), when float is involved, Opera get it wrong, sadly. To add to your examples, here: http://dev.l-c-n.com/margin_collapse/ is a series of tests about margin-collapsing between floated blocks and non floated blocks (results of some discussions on the CSS mailing list).

Posted by: Philippe at July 30, 2004 07:22 AM

Argh. My brain hurts.

Seriously, thanks for this great insight. The key lesson seems to be similar to the CSS colour guideline: if you declare one, declare them all. Assume wacky browser defaults for all margins and paddings, redefine them, and you should get consistent behaviour - although the collapsing aspect may still surprise you!

Posted by: Mark Tranchant at July 30, 2004 09:44 AM

Why is CSS so absurdly complex? Whenever I look at one of those box model diagrams, my brain shuts down. It seems like html text layout should be a lot simpler than this.

Posted by: felix at July 30, 2004 11:15 PM

@Mark.
Thanks for the kind words. And I agree, overriding browser defaults is generally not a bad idea. When the CSS specs state something like [..] assuming no border and no padding [..] there is no guarantee that your browser assumes that either ;)

@felix
If you are talking about clicking together a html layout in a WYSIWYG editor, you are probably right. Try to create a diagram illustrating a complex table layout with nested tables and spacer gifs and so on. Wouldn't look that simple either...

Posted by: Minz Meyer at August 2, 2004 04:11 PM

Nice write-up! I discovered most of this through trial and error. Like adding one pixel padding to counter the margin-collapsing. I thought it was a bug at first, but figured it out along the way. I'd been wanting a layman's-terms version of the margin-collapsing spec for a while. Glad I wandered over the Rundle's blog and somehow ended up here. Kudos!

Posted by: Seth Thomas Rasmussen at August 10, 2004 09:32 PM

Interesting write-up, although I'm struggling to really get what is going on in your first example.

You say:

"How would we then avoid the collapsing of the topmargins? I want the paragraph being 20 pixels away from the div's top border edge, how would I do that? The solution to this question lies in the question already. We have to change the height of the div by either adding a border or a padding."

Avoiding margin collapsing has nothing to do with height. It's got to go with putting something in-between the margins to stop them collapsing. In this case you are stopping the margins from being adjacent by adding padding.

However I think the top margin on the div is a bit of a red herring and not anything to do with the main problem. If you give the div a zero margin, the 2nd issue is still there.

You go on to say:

"This little addition now completely changes how the height of the div (which is still auto) is calculated. Now its height will be the distance from the outer top margin edge to the outer bottom margin edge of the paragraph."

This is the bit that I don't get. Why does it change the way the content height of the div is calculated? The specs clearly say:

"If 'height' is 'auto', the height depends on whether the element has any block-level children. If it has block-level children, it is the distance from the top border-edge of the topmost block-level child box, to the bottom border-edge of the bottommost block-level child box."

That seems pretty clear. The content height of the div should be equal to the height of the paragraph content plus the height of the border (as there is no padding). Adding border or padding to the div shouldn't have any effect on this.

So I have to admit that I'm a little confused. Unless I'm misreading or misunderstanding the specs, adding padding/borders to the div shouldn't have any effect, yet obviously it does.

Posted by: Andy Budd at August 12, 2004 01:02 PM

Hey Andy,
I am quite aware of the fact that avoiding margin-collapsing doesn't have anything to do with height. And yet it does.

By adding a padding or a border to the parent block-element, we avoid that the margins of the parent and the child element collapse. So it seems quite clear to me that the height of the parent element now has to stretch out in order to contain the whole child-element's box.

But you are right that the specs do not mention it in the CSS 2 recommendation. But you'll find it in Revision 1:
http://www.w3.org/TR/2004/CR-CSS21-20040225/visudet.html#the-height-property

Another source I found to back this up is Eric Meyer's Cascading Stylesheets: The Definitive Guide, Ed.2, pp. 170

So let's state again. I didn't mean to imply that avoiding margin-collapsing has something to do with height, but that computing height has something to do whether margins collapse or not.

Thanks for your remarks Andy, maybe this helps to clarify it a bit!

Posted by: Minz Meyer at August 12, 2004 03:01 PM

From Revision 1 of the spec:

"If it has block-level children, the height is the distance between the top border-edge of the topmost block-level child box that doesn't have margins collapsed through it and the bottom border-edge of the bottommost block-level child box that doesn't have margins collapsed through it. However, if the element has a non-zero top padding and/or top border, or is the root element, then the content starts at the top margin edge of the topmost child. (The first case expresses the fact that the top and bottom margins of the element collapse with those of the topmost and bottommost children, while in the second case the presence of the padding/border prevents the top margins from collapsing.)"

Blimey, they sure don't make it easy for people, do they! I had to read that at least half a dozen times before I really appreciated what it meant. Cheers for pointing me to that.

Of course, the simplest solution would be to add 20px top and bottom padding to the div, and remove the margin from the paragraph. You get the same result without having to worry about margin collapsing or height issues.

Posted by: Andy Budd at August 12, 2004 06:17 PM

Interesting article and eye-opening! I never knew that collapsing margins occurred in the parent-child relationships that occur between margins of block level elements (if thats a true summary of whats going on). Seems logical though. Very useful. And the default margins for the various browser is also really interesting. I wonder how the developers rationalized those different solutions for the body element. Seems like a real mess!

I think maybe an article on the difference between padding and margins is what we really need here, as that really is the crux of the whole concept behind this feature of margins. Seems to me that margins have a relationship to blocks, as padding has to content/text. As was mentioned, you can solve the problem allot easier by just sticking to padding when manipulating content and background colors and images, and use margins when controlling block or structural placement. Maybe I got that wrong, but thats been the biggest leap in logic for beginners, I've found. I wish someone would dive into that. Whenever I design now, its a whole lot easier for me to keep track of the page flow logic by lumping borders and padding with content manipulation and leave margins for block-level things only, since in actuallity, margins rarely if ever effect your content. Just apply a background color to any content block and play with poistive and negative margins on that block, then add padding and borders, and you will quickly see margins really are independent of the content and follow a ruleset that is almost totally independent of your text. At least thats the way its supposed to work (other than IE, of course).
I hope that bit of wisdom helps this article. Great article and helpful!

-Mitchell

Posted by: Mitchell at August 13, 2004 12:15 AM

Just to qualify your comment Mitchell, bear in mind that padding is pretty strongly linked to the width of a given element, if you explicitly declare that width - due to the fact that the standards say a declared width described the content area (i.e. minus padding), but IE5.5- and IE6 in Quirks Mode say that the declared width includes padding.

Posted by: Small at August 16, 2004 02:07 PM

Ive been developing a website for the place im working at and I could not get the left margins to respond through CSS for the life of me. Putting the padding-left command in changed everything - you're the only person I've ever seen make this so clear

This seems like a problem many people would run into but no one where I work has ever run into. I owe you something

serrao

Posted by: John Serrao at August 17, 2004 08:41 PM

I found your website very cool & interesting. Its a great resource. The CSS fomatting done even on this page is awesome. I don't know about the other browsers but on IE its breathtaking. I had been normally do all my rollovers using images coz the desired effect never used to come through CSS. My website address is http://www.32bytes.net and I have done quite a few projects lately. Now that I am so inspired, probably i'll be using CSS for atleast the next few projects i'm doing.

Will visit again later. Till then all the best to you.

Anirudh

Posted by: Anirudh at December 18, 2004 02:22 PM

Thanks for the clear summary! I reached your page searching for solutions to the following problem, which is NOT fixed by changing padding or borders on either element (AFAICS).
Topmost DIV has margin-bottom of 2em, 1px bottom border, and 5px bottom paddings.
Below that is a peer FIELDSET that has margin-top of 1em, and (defensive) padding of 1px.
When first displayed across all browsers, I see exactly the margin-collapsing behaviour that you write about. But now (drumroll...) toggle the display: none/block setting of the top DIV. Restoration is complete in MSIE, but Firefox 1.0 suddenly produces NO margin at all (not even a collapsed one). This is what I call a puzzler...

Posted by: at January 13, 2005 05:32 AM

Oops, sorry, did not intend my last post to be anonymous! Tried "Taster" and got an error in FF; backbutton and "Bake It" without checking that my details had vanished even though the message was intact...

Posted by: Peter at January 13, 2005 05:33 AM

the web makes my brain hurt.

nice write-up though.

Posted by: evan at March 21, 2005 08:40 PM | Let Cool (this ingredient)

This is the closest i've come to understanding the nested margin collapsing.
very close... but still not there (at least for me yet). The part about understanding how height is calculated is still a bit rough even with nicely explained pictures. How exactly is border or padding helping? I know you try to explain (by changing way it is calculated) but its just very hard for me to grasp why it is so :). gonna reread this, Andy's, and Complex Spiral's margin-collapse lessons again n' again n' again :) until i get it.

Posted by: Arian at November 29, 2005 08:22 PM

got it :) rereading that spec line in comments helped. basically they explicitly say when shouldnt and shouldnt be used. I thought mathematicfally somehow it had to calculate height and margins had to be ignored for content's height numbers to 'work'

Posted by: Arian at November 29, 2005 09:00 PM

man alive,
this is good stuff, i was going to bed two hours ago.

ok fellas, i am just really getting into css, and this write up may be a little too deep for me, but i sure enjoyed reading it. i am looking to achieve the look of formatted tables, just css. my problem is that i dont know where to start really. since i dont find anything on any of the tutorial sites i have been to that create the actual table, only the elements of the table are formatted through css, is that correct, and even then, i still have to set the table element within the table and then call the formatting for that specific table? am i confusing myself again? lol

any help would be greatfully appreciated.

later all,

Posted by: dosdawg at December 15, 2005 06:45 AM