Skip to main content

Margin Collapsing: The Most Confusing CSS Bug

Margin collapsing is the single most common source of "why isn't my margin working?" confusion in CSS. When two vertical margins meet under certain conditions, they don't add together — the larger one wins and the smaller one disappears. This is not a bug, it's a specified behavior — but it catches everyone off guard.


1. What Margin Collapsing Is

/* Without margin collapsing (what you might expect): */
.box-a { margin-bottom: 2rem; }
.box-b { margin-top: 1rem; }
/* Expected gap between them: 2rem + 1rem = 3rem */

/* What CSS actually does: */
/* Gap = max(2rem, 1rem) = 2rem — margins COLLAPSE into one */

Margin collapsing only affects vertical margins (margin-top and margin-bottom). Horizontal margins (margin-left, margin-right) never collapse.


2. The Three Scenarios Where It Happens

Scenario 1: Adjacent Siblings

Two block elements stacked vertically — their bottom and top margins collapse:

<p class="first-para">Paragraph one — margin-bottom: 2rem</p>
<p class="second-para">Paragraph two — margin-top: 1rem</p>
.first-para { margin-bottom: 2rem; }
.second-para { margin-top: 1rem; }

/* Actual gap: 2rem (not 3rem) -- the larger margin wins */
/* Collapsed margin = max(2rem, 1rem) = 2rem */
Visual:
┌──────────────────────┐
│ Paragraph One │
│ margin-bottom: 2rem │
│ │
│ [ 2rem gap ] │ ← Only 2rem (not 3rem)
│ │
│ margin-top: 1rem │
│ Paragraph Two │
└──────────────────────┘

Scenario 2: Parent and First/Last Child

When a parent has no border, padding, or block formatting context between its edge and child's margin, the margins merge into one:

<section class="c-section">
<h2 class="c-section__title">This heading's top margin collapses</h2>
</section>
.c-section { margin-top: 2rem; }
.c-section__title { margin-top: 3rem; }

/* What you expect: section starts, then 3rem gap before h2 */
/* What happens: h2's margin collapses with section's margin */
/* Combined margin = max(2rem, 3rem) = 3rem — they share one margin */
/* The h2 margin "escapes" the parent and acts as the parent's margin */
Expected: What actually happens:
┌─parent 2rem─┐ ←→ ╔══ 3rem ════╗
│ ┌─h2 3rem──┐│ ║ .c-section ║
│ │ Heading ││ ║ ┌─────────┐║
│ └──────────┘│ ║ │ Heading │║
└─────────────┘ ║ └─────────┘║
╚═══════════╝

Scenario 3: Empty Blocks

An element with no content, padding, or border — its own top and bottom margins collapse with each other:

.empty-spacer {
margin-top: 2rem;
margin-bottom: 2rem;
/* No height, no content, no border, no padding */
}
/* Actual margin: max(2rem, 2rem) = 2rem — not 4rem */

3. When Margin Collapsing Does NOT Happen

These conditions prevent margin collapsing — use them to fix unexpected behavior:

/* ── Condition 1: Any padding or border between parent and child ── */
.c-section { padding-top: 1px; } /* Even 1px defeats collapsing */
.c-section { border-top: 1px solid transparent; } /* Even transparent */

/* ── Condition 2: overflow other than 'visible' ── */
.c-section { overflow: hidden; } /* Creates block formatting context */
.c-section { overflow: auto; } /* Same effect */
.c-section { overflow: clip; } /* Modern — no scrollbar context created */

/* ── Condition 3: display: flex or grid ── */
.c-section { display: flex; } /* Flex container destroys margin collapsing */
.c-section { display: grid; } /* Grid container destroys margin collapsing */

/* ── Condition 4: position: absolute or fixed ── */
/* Absolutely positioned elements don't collapse with anything */

/* ── Condition 5: float elements ── */
/* Floated elements don't collapse with siblings */

/* ── Condition 6: display: inline-block ── */
.c-section { display: inline-block; } /* Creates its own block formatting context */

/* ── Condition 7: contain property ── */
.c-section { contain: layout; } /* Modern — creates isolated formatting context */

4. The Block Formatting Context (BFC) Concept

Margin collapsing only happens within the same Block Formatting Context (BFC). Creating a new BFC for a parent element prevents its children's margins from escaping:

/* The simplest, most reliable BFC trigger in modern CSS */
.c-card {
display: flow-root; /* Creates BFC with ZERO side effects */
/* No overflow:hidden scroll context, no flex side effects */
}

/* All these also create a BFC (but have side effects): */
overflow: hidden; /* May clip box-shadows */
display: flex; /* Changes child layout model */
display: inline-block; /* Changes block-level behavior */
position: absolute; /* Takes out of flow */
float: left; /* Also takes out of normal flow */

[!TIP] display: flow-root is the cleanest BFC trigger in 2026. No visual side effects, no overflow clipping, no layout model change — just "create a new block formatting context here."


5. Real-World Fixes

Fix 1: The "Section Title Bleeds Out" Bug

<!-- Bug: h2's margin appears outside .c-section -->
<section class="c-section">
<h2>Heading</h2>
<p>Content</p>
</section>
/* ❌ Problem */
.c-section { background: var(--color-surface); margin-bottom: 2rem; }
.c-section h2 { margin-top: 3rem; }
/* → h2's 3rem margin escapes the section, pushing section down 3rem */

/* ✅ Fix 1: Add padding-top to parent */
.c-section { padding-top: 0.1px; } /* Even tiny padding works */

/* ✅ Fix 2: flow-root (modern, preferred) */
.c-section { display: flow-root; }

/* ✅ Fix 3: Add border (acceptable if border is part of design) */
.c-section { border-top: 1px solid transparent; }

Fix 2: The "Consecutive Headings Gap" Problem

/* When you have h2 followed by h3 */
h2 { margin-bottom: 1.5rem; }
h3 { margin-top: 1rem; }

/* Gap = max(1.5rem, 1rem) = 1.5rem — intentional, usually fine */
/* But if you expected 2.5rem of gap — that's why it's smaller */

/* Fix: Use only one-sided margins consistently */
h1, h2, h3, h4 {
margin-top: 2em;
margin-bottom: 0; /* Control spacing with top only */
}
/* Now: no top-bottom collapse because only top margins applied */

Fix 3: WordPress-Specific (Gutenberg blocks)

/* Gutenberg wraps blocks in <div> containers */
/* Margins between .wp-block elements collapse */

/* Fix: Add to your child theme */
.wp-block-group,
.wp-block-cover,
.entry-content > * {
display: flow-root; /* Prevent margin escaping semantic containers */
}

6. Spotting Margin Collapsing in DevTools

Chrome DevTools:
1. Inspect element → click the element in Elements panel
2. In the Styles panel → bottom: the box model diagram
3. Orange = margin. Hover over each side to see computed value
4. If margin-bottom looks smaller than expected → collapsing

Firefox DevTools (better for this):
1. Inspect element
2. Box Model panel shows collapsed margins in a different shade
3. Firefox shows "collapsed margin" labels on the diagram

7. Margin Collapsing Rules Summary

COLLAPSES:
✅ Adjacent siblings (top margin of B + bottom margin of A)
✅ Parent + first child (parent top = child top, no separator)
✅ Parent + last child (parent bottom = child bottom, no separator)
✅ Empty block's own top + bottom margins

DOESN'T COLLAPSE:
❌ Horizontal margins (left/right) — NEVER collapse
❌ Inside flex containers
❌ Inside grid containers
❌ When overflow is not 'visible'
❌ When there's any padding between parent + child
❌ When there's any border between parent + child
❌ Absolutely/fixed positioned elements
❌ Floated elements
❌ display: flow-root / inline-block elements
❌ Between inline and block elements

COLLAPSE RESULT:
Positive + Positive → Largest wins
Negative + Negative → Most negative wins
Positive + Negative → Sum (they partially cancel)
Example: 2rem + (-1rem) = 1rem gap

8. The Modern Strategy: Avoid Reliance on Collapsing

The cleanest long-term approach is to write CSS that doesn't depend on margin collapsing at all:

/* Strategy: margin-top only (on everything except the first child) */
* + * { margin-top: 1em; } /* Lobotomized Owl selector */

/* Or: use gap in flex/grid containers instead of margins */
.o-stack {
display: flex;
flex-direction: column;
gap: 1.5rem; /* gap: never collapses */
}

/* Or: padding-block for section spacing, not margin-block */
.l-section { padding-block: clamp(3rem, 8vw, 8rem); }
/* padding never collapses */

[!IMPORTANT] Using gap in flex or grid containers is the most reliable way to control spacing between elements in 2026 — it never collapses, never "escapes" parents, and is visually predictable.