Web Platform · CSS Layout

Adjoining Margins and the Block Formatting Context

How block-level boxes stack in normal flow, when adjacent vertical margins merge into a single offset, and why establishing a formatting context stops the merging.

Two sibling paragraphs with a bottom margin of 20 pixels on the first and a top margin of 30 pixels on the second sit 30 pixels apart, not 50. The result is not a quirk of one browser; it is specified behaviour called margin collapsing, and it follows from a model in which a block box’s margin describes a minimum separation from whatever is next to it rather than a slab of space owned by the box.

Collapsing is easy to state for siblings and difficult to predict in nested structures, because the same rule applies between a parent and its first or last child, through empty boxes, and across boxes whose only connection is that they are adjacent in the block axis. Understanding the block formatting context is mostly a matter of knowing which boxes are exempt from the rule and which are not.

How unpositioned block boxes are laid out

A block-level box in normal flow takes the full inline size of its containing block. Its inline margins are honoured, its inline size is resolved so that margin, border, padding and content together fill the containing block, and consecutive block-level siblings are stacked in the block direction. The vertical position of each box is determined by the previous box’s bottom border edge plus whatever separation the margins produce.

The margin itself is computed but not “occupied” by anything. When two vertical margins meet, the browser does not lay out a gap of the first margin and then a gap of the second; it computes a single collapsed margin and applies that. This is why a bottom margin and a top margin do not add.

Content that establishes inline formatting participates differently: an anonymous block box wraps runs of inline content, and a line box inside it is what actually gives the block height. A block with no content, no padding, no border and a height of zero has no line boxes at all, which makes its own top and bottom margins adjacent and lets them collapse with each other.

The collapse rules in full

CSS 2.1 §8.3.1 defines when two margins are adjoining. Vertical margins are adjoining if the boxes are in the same block formatting context, nothing separates them, and at least one of several structural conditions holds:

  • They are the bottom margin of a box and the top margin of its next in-flow sibling.
  • They are the top margin of a box and the top margin of its first in-flow child, provided the parent has no top border, no top padding, and does not establish a new formatting context.
  • They are the bottom margin of a box and the bottom margin of its last in-flow child, under the same conditions at the bottom edge, and the parent’s height is auto rather than a specified value.
  • They are the top and bottom margins of a box that has zero computed height, no border, no padding and no in-flow content.

The arithmetic for a collapsed margin is not a sum. The result is the largest positive margin plus the smallest (most negative) negative margin. A 20-pixel bottom margin against a 30-pixel top margin yields 30 pixels. A 20-pixel bottom margin against a −10-pixel top margin yields 10 pixels, because the margins overlap rather than pushing apart. Two negative margins yield the more negative of the two. With three or more adjoining margins, the same rule applies to the whole set at once.

Horizontal margins never collapse. They are resolved against the containing block with the over-constrained equation and may be negative, but they always occupy their own space.

Why the design exists

The rule solves a problem that a naive additive model creates: spacing around nested containers. Given a page wrapper with a top margin and an <h1> inside it with its own top margin, the additive model places the heading’s margin below the wrapper’s border, pushing the wrapper down and leaving the heading pressed against the inside of the wrapper’s top edge. Every nesting level would compound the gap, and each child margin would have to be zeroed to prevent it.

Collapsing makes the two margins describe the same separation, so the larger one wins. The heading’s margin escapes the wrapper and pushes the whole wrapper away from the preceding content. Nested sections with 1em margins all around then produce visually even 1em gaps rather than accumulating multiples of 1em.

The cost of this convenience is that a container cannot be given internal top spacing via a child’s margin unless a border, padding, or formatting context separates them. This is the origin of the widespread practice of giving a container padding-top: 1px or overflow: hidden to stop the escape, and of the more direct display: flow-root, which establishes a block formatting context without otherwise changing the box.

Establishing a formatting context

A block formatting context is a region in which block-level boxes are laid out according to the block rules, and crucially, a boundary across which vertical margins do not collapse and around whose edges floats are contained. Any of the following establishes one:

  • float with a value other than none, and position: absolute or fixed.
  • display: inline-block, table-cell, table-caption, flow-root.
  • A flex or grid container’s children, and flex and grid containers themselves.
  • overflow with a value other than visible or clip, which is the older idiom.
  • contain: layout, contain: paint, or contain: content.

Margin collapsing also does not occur between flex items or grid items. In a flex container, the margins of the items are ordinary offsets along the main and cross axes; two items with 20-pixel and 30-pixel margins have 50 pixels between them. This is one reason a layout migrated from floats to flexbox can gain vertical space it did not previously have.

/* Margins collapse through this wrapper's top edge. */
.card {
  margin-top: 2rem;
}

/* No collapse: a formatting context and a border separate the edges. */
.panel {
  display: flow-root;
  border-top: 1px solid #ccc;
}

display: flow-root is the smallest hammer. It creates a block container that establishes a block formatting context with no other side effects on the box’s own model, unlike overflow: hidden, which also turns the element into a scroll container candidate and clips descendants that overflow.

Where the model surprises

Several cases produce results that look wrong until the adjoining condition is checked.

A floated child does not push its parent’s height. A block containing only floats has zero content height, because floats are taken out of normal flow for the purpose of the parent’s height calculation. Establishing a formatting context on the parent makes it grow to enclose them, which is the mechanism behind the clearfix idiom’s modern replacement.

A cleared box’s top margin does not collapse with its parent. Clearance counts among the things that stop two margins being adjoining, alongside line boxes, padding and borders in CSS 2.1 §8.3.1, and it acts as spacing added above the box’s top margin. A cleared box’s margins can still collapse with those of following siblings, but the result cannot collapse with the parent’s bottom margin, so the space the margin would have absorbed is spent pushing the box past the float instead.

Empty boxes swallow margins. A <div> with no content and no height contributes nothing visible but can pass a parent’s margin through to its own siblings, because its top and bottom margins collapse together and then with the surrounding margins. A zero-height element is not always inert.

A specified parent height blocks bottom collapse. If the parent’s height is set explicitly rather than auto, the last child’s bottom margin cannot escape, and it is not added to the parent’s height either. The gap between the parent’s bottom border and whatever follows is then whatever the parent’s own margin and padding provide, which is one reason a height introduced during debugging changes spacing at the bottom of a box but not at the top.

Percentages resolve against the inline size. Vertical margins given as percentages, including margin-top: 5%, resolve against the containing block’s inline size, not its block size. A 5% top margin on a box that is 1000 pixels wide is 50 pixels regardless of the container’s height. The behaviour follows from the circularity that would arise if the vertical margin depended on a height that might in turn depend on the margin.

Negative margins shift but do not collapse differently. A negative bottom margin pulls following content upward and can make boxes overlap. When it collapses with a positive margin, the result is the sum of the largest positive and the most negative, so a −40-pixel bottom margin against a 10-pixel top margin produces a −30-pixel separation and visual overlap.

These behaviours are all consequences of one model. Its predictability comes from one test applied to any two vertical margins: whether they are adjoining, which requires that the boxes be in the same formatting context with nothing between them. That test determines whether the offsets add or merge. That same question reappears in the containing block calculations described in Used Values and the Containing Block, where the box model’s edges are resolved into concrete pixel lengths.

References

  1. CSS 2.1, Section 8.3.1: Collapsing margins — W3C
  2. CSS Display Module Level 3 — W3C
  3. CSS Box Model Module Level 3 — W3C

The archive date above places this article in the library, its archive and its feed. It is an assigned date for ordering, not a historical publication record.

#box-model#margin-collapse#block-formatting-context#normal-flow