What I Learned Today 💡 May 28, 2017

Darn you Margin Collapsing ಠ_ಠ

I have been reading Mikito Takada’s Learn CSS Layout The Pedantic Way. In the book, he uses The CSS Specification as a primary source for the study of CSS Layout. In terms of covering CSS technicalities, it’s a more digestible medium than the CSS Specification itself. So, if you’re wanting to learn the nitty-gritty of CSS layout technicalities, I highly recommend reading his work.

I was reading his discussion of stacking. I went to CodePen to try an experiment based on the reading.

In doing so, I discovered something sinister.

POP QUIZ: What will this markup render as?

My title probably gives it away. Oh well, render this markup in a (modern) browser, compare it with your expectations, then return here.

This is how it rendered for me (on CodePen):

Hmm… It looks like “Hello!” doesn’t have a “margin-top”?

I did not expect this. I expected:

<div class="child1">Hello!</div> to be 50px from the top of <div class="parent"> .

to be from the of . <div class="child2">Bye!</div> to be 50px from the bottom of <div class="parent">.

Upon inspection with DevTools, I realized what was happening.

The margin-top was overflowing out of the parent’s top. The same goes for <div class=”child2”>, just mirrored on the bottom. :/

I thought container elements expanded to fit their children’s margins!

- A hopeful, näive Anton (me right now)

I Googled for an answer. Apparently, the answer was margin collapsing.

I was somewhat familiar with margin collapsing in the past. I knew that sibling box-margins overlap. And that is still true here.

The bottom-margin of <div class=”child1”> OVERLAPS the top-margin of <div class=”child2”>

But another kind of margin collapsing was present here. It was the cause for the child-margin-overflow-out-of-parent phenomenon.

According to MDN, there are 3 fundamental cases of Margin Collapsing:

Adjacent Siblings (we just discussed this)

Parent and first/last child

Empty Blocks (Basically a block with no content/padding/border/height will have its margin-top and margin-bottom collapse)

Parent and first/last child is the cause of the overflow phenomenon here.

Here are the stipulations for Parent and first/last child Margin Collapsing (paraphrased from MDN):

Consider a Parent/Container Element with some child elements.

The first child’s margin-top WILL overflow out of the parent’s content-top UNLESS any of the following conditions are met:

Parent has a (non-zero) border-top

Parent has a (non-zero) padding-top

There is inline-content (e.g. text) before the first-child.

(e.g. text) before the first-child. The child has display: inline-block .

(Some specific use of clear and floats that I didn’t understand but I’m including here for completeness)

and that I didn’t understand but I’m including here for completeness) The Parent is asserting a block-formatting-context. More on this later.

If any of the above conditions are met, the Parent’s content-top will expand to accommodate the first child’s margin-top .

The last child’s margin-bottom WILL overflow out of the parent’s content-bottom UNLESS any of the following conditions are met:

Parent has a (non-zero) border-bottom

Parent has a (non-zero) padding-bottom

There is inline-content (e.g. text) after the last-child.

(e.g. text) after the last-child. The child has display: inline-block .

The Parent’s height or min-height is set in such a way that it manually accommodates the last child’s margin-bottom . This is a trivial case.

or is set in such a way that it manually accommodates the last child’s . This is a trivial case. The Parent is a asserting a block-formatting-context. More on this later.

Block-Formatting Context

I don’t want to explain what a block-formatting context is because I don’t understand it well enough to explain, and also because it would be out of the scope of this article.

Just know that a Parent element will exert it upon its Children when:

The Parent is the root element of the document (e.g. <body> or <html> )

of the document (e.g. or ) The Parent is floated (has float value other than none )

(has value other than ) The Parent is absolutely positioned (e.g. position: absolute or position: fixed )

(e.g. or ) The Parent has display: inline-block

The Parent has display: table-cell

The Parent has display: table-caption

The Parent has an overflow value other than the default visible .

value other than the default . The Parent has column-span: all (This doesn’t work in Chrome. Browser bug.)

Wat do?

What should I do in this situation then?

I Googled around, and I found that there are plenty of “hacky” ways to circumvent this behavior.

After even more googling, I found this article on /r/webdev, where redditor /u/Mestyo offers a suggestion for avoiding spilled-margins that suits my particular use case:

A generic tip is to avoid any margins leaking out of containers; the first element of a container shouldn’t have a margin-top , and the last element of a container shouldn't have a margin-bottom .

So, I opted to use the Container’s padding-top and padding-bottom to space the first child and last child respectively. I removed the margin-top from the first child and the margin-bottom from the last child, as /u/Mestyo advised.

Here’s how it went:

This worked exactly as I had originally intended!

You can see that the container’s padding-top and padding-bottom are taking on the roles of the first child’s margin-top and the last child’s margin-bottom respectively.

This was actually pretty fun and enlightening to discover. No doubt that I will run into “CSS gotchas!” like this in the future.

But when I do, I’ll be a little less stupid on the subject 😂

Opinions expressed in these articles do not reflect those of my employer.