I was spacing two sections on a page. The first one had mb-10, the one below had mt-6, and the actual gap between them was 40px, not 64px. I spent a couple of minutes in DevTools toggling properties before I remembered what this was. It wasn’t a bug, it was margin collapsing, and I had not thought about it in years because most of my layouts use flex or grid with gap, which bypasses it entirely. So I went down a small rabbit hole to actually understand why CSS does this instead of just adding the margins like I expected.
What is happening
When two vertical margins meet in normal flow, they do not add up; the larger one wins and the smaller one disappears. So 40px on the bottom and 24px on the top give you 40px of space, not 64px. Horizontal margins always add, which is why ml-10 next to mr-6 never surprises anyone, but vertical ones collapse into each other. The rule is only about the vertical axis, and only inside normal block flow.
Why it exists
CSS was written for documents first, not apps. Think of a long article with paragraphs, headings, and lists. If every element contributed its full margin in both directions, the spacing between a heading with mb-8 and a paragraph with mt-6 would keep inflating in ways no author actually wanted; the page would feel airy in some places and cramped in others depending on which elements happened to sit next to each other. Collapsing margins keeps vertical rhythm consistent across a document. Each element says “I want at least this much space around me”, and the browser picks the larger request instead of stacking them.
That logic is perfectly fine when you are writing prose. It gets weirder when you are writing layout code, because an app layout is almost never trying to preserve document rhythm; it is trying to hit exact pixel values.
Where you actually hit it
The obvious case is two adjacent siblings. Box A has a 30px bottom margin, box B has a 20px top margin, the gap between them is 30px. That is the one most people eventually learn and the one that was biting me on my page.
Visual
max(40px, 20px) = 40px. margin-bottom wins. The other is discarded.
Code
.box-a {
margin-bottom: 40px;
}
.box-b {
margin-top: 20px;
}
/* actual gap: 40px */
/* max(40, 20) = 40 */The trickier case is a parent and its first child. Give the child a margin-top. If the parent has no padding or border at the top, that margin does not create space inside the parent between the top edge and the child. Instead it escapes and pushes the entire parent down from whatever sits above it. The space ends up outside the parent, not inside it. You can spend a while in DevTools toggling things on the child before you notice it is the parent that needs fixing. Adding padding-top or a border to the parent is usually all it takes.
Visual
No padding. The child's margin escapes and pushes the parent down.
Code
.parent {
background: green;
/* no padding — margin escapes */
}
.child {
margin-top: 40px;
background: blue;
}Collapsing only applies inside normal block flow, so flex and grid items do not collapse. That is one of the quieter reasons layouts feel more predictable once you switch to them. Floated elements, absolutely positioned elements, and anything that creates a new block formatting context (overflow: hidden, display: flow-root, and friends) also opt out. Padding or a border on the parent stops the escaping-child case too, which is usually the fix when you have a section whose heading is pushing the whole thing away from whatever sits above it.
What I actually do about it
Most of my layouts are flex or grid containers with gap, and gap does not collapse. That is the real reason I rarely run into this anymore. When I do hit it, it is almost always inside a prose block or a server-rendered markdown page, and the fix is usually to add display: flow-root to the container or to swap the margins out for a wrapper that uses space-y-*. I used to think margin collapsing was a piece of CSS trivia I had to keep remembering. It is closer to a feature I mostly do not touch, because my default layout primitives never trigger it in the first place.