Type something to search...
Margin Collapse in CSS: Why Your Spacing Behaves Strangely

Margin Collapse in CSS: Why Your Spacing Behaves Strangely

You give a heading margin-bottom: 20px and the paragraph after it margin-top: 30px. You'd expect 50px of space between them. The browser shows 30px. Or you add margin-top to the first element inside a card, and instead of pushing the content down inside the card, the whole card moves down the page.

Neither is a bug. Both are margin collapsing, one of the oldest and most misunderstood behaviors in CSS. It was designed for documents, so that paragraphs and headings get sensible spacing without extra effort, and it still does that job well. But if you don't know the rules, it looks like the browser is ignoring your code.

This guide explains exactly when margins collapse, how the resulting size is calculated, the three situations where it happens, and the cleanest ways to prevent it when you don't want it.

What Margin Collapsing Is

When two vertical margins touch, with nothing in between them, they can combine into a single margin. Instead of adding together, the result is the larger of the two.

<h2 class="title">Section title</h2>
<p class="intro">Intro paragraph.</p>
.title {
  margin-bottom: 20px;
}

.intro {
  margin-top: 30px;
}

The space between the heading and the paragraph is 30px, not 50px. The two margins overlap, and the larger one wins.

The reasoning makes sense for text. A paragraph might have 1em of margin above and below. Stack ten paragraphs, and you want 1em between each pair, not 2em. Collapsing gives you that without having to strip margins off the first or last element.

The Key Rules

Margin collapsing is narrower than people often assume. It only happens under specific conditions:

  • Only vertical margins collapse. More precisely, only block-direction margins: top and bottom in normal horizontal writing. Left and right margins never collapse.
  • Only in normal block layout. Margins of flex items and grid items never collapse. Neither do margins of floated or absolutely positioned elements, or inline-block elements.
  • The margins must be adjoining. Nothing can separate them: no border, no padding, no content, no line box, and no clearance.

If any of those conditions isn't met, margins simply add up the way you'd expect.

How the Collapsed Size Is Calculated

When margins collapse, the result depends on whether they're positive or negative:

  • All positive: the result is the largest margin. 20px and 30px collapse to 30px.
  • All negative: the result is the most negative margin. -10px and -25px collapse to -25px.
  • Mixed: take the largest positive margin and add the most negative one. 30px and -10px collapse to 20px.

More than two margins can collapse together too, in which case the same rule applies across all of them.

Case 1: Adjacent Siblings

This is the most common and least surprising case. The bottom margin of one block and the top margin of the next sibling collapse:

.card {
  margin-block: 1.5rem;
}

Between two consecutive cards, there's 1.5rem of space, not 3rem. For most layouts, that's exactly what you'd want, and it's why the combination of "margin on both sides" and collapsing works well for flowing content.

Case 2: Parent and First or Last Child

This is the case that confuses people. If a parent has no border, padding, or inline content separating it from its first child, the child's top margin collapses with the parent's top margin. The same applies to the last child's bottom margin and the parent's bottom margin.

<section class="panel">
  <h2 class="panel__title">Panel title</h2>
  <p>Panel content.</p>
</section>
.panel {
  background: #e0f2fe;
}

.panel__title {
  margin-top: 2rem;
}

You'd expect the heading to sit 2rem below the top edge of the blue panel. Instead, the panel's blue background starts right at the heading, and there's 2rem of empty space above the panel. The child's margin has escaped through its parent.

What happened is that the panel's top margin (0) and the heading's top margin (2rem) are adjoining, so they collapse into one 2rem margin. That combined margin belongs to the outside of the panel, so it pushes the whole panel down.

This is also why a page sometimes has an unexpected gap at the top of the viewport: an h1 inside main inside body, none of which have padding, and the heading's margin collapses through all of them.

Case 3: Empty Blocks

If a block has no content, no padding, no border, and no height, its own top and bottom margins are adjoining, so they collapse with each other. The result can then collapse with neighbors too.

<p>First paragraph.</p>
<div class="spacer"></div>
<p>Second paragraph.</p>
p {
  margin-block: 1rem;
}

.spacer {
  margin-block: 2rem;
}

You might expect 1rem + 2rem + 2rem + 1rem of space. Instead, all four margins collapse into one, and the gap between paragraphs is 2rem, the largest of them. Empty elements used purely for spacing often don't produce as much space as their margins suggest.

How to Stop Margins from Collapsing

When collapsing gets in your way, you have several options. Some are cleaner than others.

Use Padding Instead

For the parent-and-child case, the simplest fix is often padding on the parent:

.panel {
  background: #e0f2fe;
  padding: 2rem;
}

.panel__title {
  margin-top: 0;
}

Padding lives inside the parent's background and doesn't collapse. If the panel has a visible background or border, padding is usually the right tool for inner spacing anyway.

Create a New Block Formatting Context

Margins don't collapse through the boundary of a block formatting context (BFC). An element that establishes a new BFC keeps its children's margins inside it. The modern, side-effect-free way to create one is:

.panel {
  display: flow-root;
}

display: flow-root exists precisely for this. It behaves like display: block in every way, except it establishes a new block formatting context. It also contains floats, which makes it the modern replacement for the old clearfix hack.

Other things that create a BFC, like overflow: hidden or overflow: auto, also stop the collapse, but they have side effects. overflow: hidden clips content, including focus rings and dropdowns, and overflow: auto can create unexpected scrollbars. Prefer flow-root.

Add a Border or Padding, Even a Tiny One

Any nonzero border or padding between the margins separates them:

.panel {
  padding-top: 1px;
}

This works, but it's a hack. The 1px is visible in precise layouts and confuses whoever reads the code later. Use flow-root instead where possible.

Switch to Flexbox or Grid

Margins of flex and grid items never collapse. If a container is a flex column or grid, its children's margins add up normally and stay inside the container:

.panel {
  display: flex;
  flex-direction: column;
}

In practice, this is usually the reason people see margin collapse "go away" when they refactor a component to flexbox. It's also a reason to prefer gap for spacing inside flex and grid containers: it's predictable and never collapses.

Use margin-trim, Where Supported

The margin-trim property lets a container remove the margins of its first and last children against its edges:

.panel {
  margin-trim: block;
}

This is exactly what many people want from the parent-child case. However, margin-trim is currently supported only in Safari. Treat it as a progressive enhancement, and don't rely on it for layout that must be right in every browser.

Designing with Margin Collapsing, Not Against It

Collapsing isn't always a problem to solve. It can make spacing systems simpler.

A Single-Direction Margin Strategy

Many teams avoid collapsing surprises by putting margin on only one side of elements, usually the bottom or top, never both:

h1,
h2,
h3,
p,
ul,
ol,
figure {
  margin-block: 0 1rem;
}

With margins only on the bottom, siblings never collapse into each other because there's only one margin between them. The parent-child case still applies to the last child, but that's easy to handle.

The Stack Pattern

Another approach is to remove default margins and add space only between siblings:

.flow > * {
  margin-block: 0;
}

.flow > * + * {
  margin-block-start: var(--flow-space, 1em);
}
<article class="flow">
  <h2>Heading</h2>
  <p>Paragraph one.</p>
  <p>Paragraph two.</p>
</article>

No child ever has margin on its outside edges, so nothing escapes the parent, and siblings have exactly one margin between them. You can adjust spacing for specific elements by changing --flow-space:

.flow > h2 {
  --flow-space: 2.5em;
}

Using Collapse on Purpose

Sometimes collapsing is exactly the right behavior. For example, a heading with a large top margin that follows a paragraph with a small bottom margin gets the heading's larger spacing, without you having to write a special rule like p + h2. Default browser styles rely on this, which is why unstyled HTML documents have reasonable vertical rhythm out of the box.

Debugging Margin Collapse

When spacing looks wrong, open DevTools and select the elements involved. The box model diagram in the Computed tab shows each element's margin. If the margins are larger than the visible gap, or the gap is outside a parent when you expected it inside, collapsing is the likely cause.

Hovering an element in Chrome, Edge, or Firefox also highlights its margin in orange on the page. When a child's margin highlight extends outside its parent's background, you're looking at a parent-child collapse.

A quick way to confirm: temporarily add display: flow-root to the parent, or outline: 1px solid red to see edges without affecting layout. If adding flow-root fixes the spacing, collapsing was the issue.

Quick Reference

SituationDo margins collapse?
Adjacent block siblings (vertical)Yes
Parent and first/last child, nothing betweenYes
Empty block with no height, padding, or borderYes, with itself
Horizontal marginsNever
Flex or grid itemsNever
Floated or absolutely positioned elementsNever
Parent with display: flow-rootNot with its children
Parent with padding or borderNot with its children

Conclusion

Margin collapsing is a deliberate feature of CSS block layout. Adjoining vertical margins combine into one, using the largest positive value (adjusted by the most negative one, if any). It happens between siblings, between a parent and its first or last child, and within empty blocks, but never horizontally and never in flexbox or grid.

When it gets in your way, reach for padding, display: flow-root, or a flex or grid container with gap. And for flowing content, consider working with it instead: a consistent single-direction margin or a stack pattern gives you predictable spacing without any surprises.

Tags :
Share :

Related Posts

A Complete Guide to CSS Container Queries

A Complete Guide to CSS Container Queries

For more than a decade, responsive design meant one thing: media queries. You asked the browser how wide the viewport was and adjusted your layout ac

Continue Reading
A Comprehensive Guide to Installing Next.js

A Comprehensive Guide to Installing Next.js

Next.js has emerged as a powerful framework for building React applications, offering features like server-side rendering, static site generation, an

Continue Reading
Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

CSS has evolved significantly, and modern tools like clamp(), min(), and max() are powerful game-changers in dynamic styling. If you’ve struggl

Continue Reading