How the browser handles adjacent HTML elements and nested HTML elements with regards to creating a single vertical margin, an aspect of CSS known as 'margin collapsing', is not a difficult topic to understand. That said, it can get a bit tricky in more advanced scenarios – such as when nested elements and negative margins are encountered.
After reading this article you will be fully versed in the intricacies of margin collapsing and quite capable of speaking condescendingly to developers and designers that aren’t (I don't condone that sort of behaviour, but each to his own). We will start off nice and easy with a quick recap of the CSS box model. After that we’ll move on to the basics of vertical margin collapsing. Finally, we will tackle those advanced scenarios that seem to cause so many problems.
Box Model Review
Seeing as you are reading an article about vertical margin collapsing, I assume a certain level of familiarity with the CSS box model. You can find good coverage of it here. The basic gist of it is as follows.
Most ‘block level elements’ on your web page, divs and paragraphs for example, can be considered content contained within a box. Looking at the illustration below, you can see that ‘Hello World’ (the content) can have padding, a border, and a margin.
This conceptual box is your friend as it can be positioned; assigned a width, a height, given a colour, so on and so forth. But all we are really interested in today is the not-always-intuitive way that margins interact with each other.
In the image above you can see the various aspects of a block level element that affect the amount of space around a given piece of content. This article primarily deals with the outermost ‘margin’ setting and, more specifically, how and when collapsing occurs between these margins.
The Basic Scenario
Imagine a web page with two simple paragraphs. The first paragraph has a margin of 15 pixels and the second paragraph has a margin of 20 pixels. What is the distance from the bottom of the first paragraph to the top of the second paragraph? The formula I use below is totally incorrect, but it is where intuition may lead you.
Bottom Margin + Top Margin = 15px + 20px = 35px
This answer, 35px, is not right. The correct answer is actually 20px. The correct formula for calculating the distance is as follows.
Max(Top Margin, Bottom Margin) = Max(15px, 20px) = 20px
What we are saying here is that the largest margin stays and the smallest one is collapsed to 0. There you have it, this is margin collapsing in action. It doesn’t really require a mathematical expression to illustrate a seemingly simple 'largest margin wins' result such as this, but I will stick with this approach because it will help a great deal when we encounter the advanced scenarios shortly.
TIP: margin collapsing only occurs where two (or more) top or bottom margins are touching, and it doesn’t happen at all with left and right margins.
Let’s ramp it up a notch. Have a look at the code snippet below.
We have 3 paragraphs – the first one with the default settings for margins, the second one specifically set with a top margin of 100px and a bottom margin set to 30px, and then we have the last paragraph which is identical to the first.
Let’s recognise the fact that the second paragraph, though empty, still exists. Therefore its margins are still respected by the browser. Let’s also assume that the default margins for paragraphs are 16px (which is a reasonable assumption). How do we figure out the distance between the first paragraph and the third paragraph?
Max(First Para bottom margin, Second Para top margin, Second Para bottom margin, Third Para top margin) = Max(16,100,30,16) = 100
As you can see, the same formula applies as it did in the previous simpler case. It's not difficult at all is it?
Have a go at this next one. Given the code below, the knowledge that div elements default to a margin of 0px, and that paragraph elements default to a margin of 16px, can you figure out the distances between each element?
The gap between the div and paragraph is 16 pixels in both cases. The distance between the two paragraphs is also 16 pixels. Hopefully it is clear why.
Negative margins, though often misunderstood, are a great tool in the web developer’s arsenal. If you aren’t already familiar with them you can learn about them here.
Negative margins are calculated a bit differently with regard to collapsing, but it’s still fairly easy. The logic works like this:
Get a list of all margins that are touching.
Forget all of the negative margins (for now).
Perform the collapse as normal (remember, no negative margins due to step 2).
Now, take the largest of the negative margins that we have so far totally ignored, and subtract it from the computed margin in step 3.
Consider the code below.
Lets apply the logic just discussed to this code listing.
- Step 1
- 16, -5, 100, -50, 16, 16 (this is the list of all the margins that are touching)
- Step 2
- 16, 100, 16, 16 (this is the same list, but with the negative margins removed)
- Step 3
- max(100,16) = 100 (derived via the same formula we have been using all along, the answer is 16px)
- Step 4
- Subtracting the largest negative margin. Let’s bring back the largest of the negative margins, which is 50, so: 100 - 50 = 50
So, the distance between the first paragraph and the last paragraph is 50 pixels. Once you know the logic behind all this it's not at all difficult to work things out.
The manufacturers of painkillers love nested margins. These cause so many headaches for developers and designers. Though not very intuitive, they are actually quite easy to understand if you take the time to wrap your head around their nuances.
Let’s study the code below for a moment. Here we have a div within a div, a very common scenario. You can see that there are two margins touching each other; the 100px inner divs top margin is touching the outer divs 50px top margin.
Given what we already know, we can calculate the answer easily enough by getting the maximum of the two touching margins. Max(50,100) tells us that the collapsed margin will be 100px. But here’s an interesting question: where should the margin actually appear? The 100px margin won out, so surely the margin would be placed between the inner div and the outer div, as illustrated by a) on the left below?
Believe it or not, the correct answer is illustrated by b). Despite the fact that the 100px margin ‘belonged’ to the inner div, it is looks like it has been applied to the outer div with regard to where it appears. Another way to look at it is that it is still applied to the inner div but 'sticks out' through the outer div.
So the logic (yes, there’s logic to this!) is as follows. The calculated margin always appears as far out as possible. That is to say, it appears outside the outer-most element whose margin is taking part in the collapse. Yes, I know, not at all intuitive.
How do you get the collapsed margin to appear where you (probably) expected it to be, between the inner and outer div? Remember, margin collapsing only takes effect when margins touch. So the answer is simple; stop the margins from touching. You can do this by adding a single pixel border (or padding) into the mix.
Check out the code below. This is very similar to the code listing above. The only difference is the addition of the border rule to the inner div. The result is that the margins no longer touch and this now acts like a) instead of b).
I hope this has shed some light on margin collapsing. It's probably not the most intuitive aspect of CSS, but the takeaway here is that there is some logic at play and, once you have learned it, the mystery and confusion suddenly dissapears!