Skip to main content

Margin Collapse Rules

Free2017-11-12#CSS#CSS margin collapse#collapsed margin#合并外边距#外边距折叠#CSS clearance

An interesting but difficult-to-understand part of the CSS box model.

Foreword

Margin collapse rules are arguably the most complex part of the CSS box model, bar none. This is because this topic involves many concepts that are not easy to grasp, such as clearance, normal flow/in-flow, Block Formatting Context (BFC), line boxes, inline boxes, bidi (bidirectional environments), and so on.

The CSS box model is more than just 7 horizontal properties + 7 vertical properties:

margin
  border
    padding
      width/height

P.S. Reminds me of the high heels joke—"Not only padding, but added margin today as well."

Related content includes at least:

  • context-box and border-box

  • How padding/margin percentages are calculated

  • background and padding/margin/border

  • Negative margin values

  • margin collapse

Box models are the fundamental units in the visual formatting model and an essential part of the CSS layout model.

The CSS box model describes the rectangular boxes that are generated for elements in the document tree and laid out according to the visual formatting model.

(Cited from 8 Box Model)

Therefore, the box model is the first layer of abstraction CSS establishes over the document tree and the part where CSS layout control directly associates with document elements. Since margin collapse is one of the factors directly affecting vertical formatting, it is necessary to understand it deeply.

1. Classic Scenarios

In the following examples, assume the User Agent (UA) has no default stylesheet, and undeclared style properties take their initial values according to the specification.

Furthermore, assume all UAs are compliant with CSS specifications.

1. Margin Collapse Between List Items

li {
    margin: 8px;
}

So what is the spacing between list items?

.li-case1 li {
    margin: 8px;
    /* 添个上内边距 */
    padding-top: 1px;
}

.li-case2 li {
    margin: 8px;
    /* 添个下边框 */
    border-bottom: 1px solid;
}

In case1 and case2, what are the respective spacings between list items?

2. Deeply Nested Margin Collapse

/* 缩进表示对应文档结的构嵌套关系 */
div.outer,
  div.container,
    div.content,
      div.inner {
    margin: 10px;
    min-width: 100px;
    min-height: 100px;
}

What is the rendering result of these 4 nested divs?

div.outer,
  div.container,
    div.content,
      div.inner {
    margin: 10px;
    min-width: 100px;
    min-height: 100px;
    /* 添个border */
    border: 1px solid;
}

How about now?

div.outer,
  div.container,
    div.content,
      div.inner {
    margin: 10px;
    /* 删掉min-width, min-height和border */
}

How about now?

3. Margin Collapse with Clearance

div.container {
    border-top: 1px solid;
    background: #ccc;
    margin-bottom: 60px;
}
  /* 缩进表示对应文档结的构嵌套关系 */
  div.float {
      float: left;
      width: 100px;
      height: 50px;
  }
  div.following-float {
      clear: left;
      margin-top: 50px;
  }
div.following-container {
    color: red;
}

What is the distance from the top of the red text to the bottom of .following-float?

div.container {
    border-top: 1px solid;
    background: #ccc;
    margin-bottom: 60px;
}
  /* 缩进表示对应文档结的构嵌套关系 */
  div.float {
      float: left;
      width: 100px;
      height: 50px;
  }
  div.following-float {
      clear: left;
      /* 把50改成49 */
      margin-top: 49px;
  }
div.following-container {
    color: red;
}

How about now?

What if 50 is changed to 0 and 51? What would happen in each case?

P.S. The answers to these questions are still unknown at this moment because the Demo hasn't been written yet ;-) so we have enough time to take a serious guess.

2. Conditions for Collapse

What kind of margins collapse?

Horizontal margins do not collapse. Adjoining vertical margins collapse, except in two special cases:

  • Margins of the root element box do not collapse.

  • If the top and bottom margins of an element with clearance are adjoining, its margins collapse with the adjoining margins of its following siblings, but the resulting margin does not collapse with the bottom margin of the parent block.

Skip rule 1; applying margins to the root element is generally illogical.

Rule 2 introduces a new concept called "clearance". It seems related to the clear property, and indeed it is intuitively the gap formed when an element is moved due to the clear property, see CSS specification 9 Visual formatting model. This implies two key points:

  • Having the clear property.

  • And (the clear property) caused the element's position to move.

If these two conditions are met, an element is said to have clearance.

Note: If the clear property is applied but the element's actual position remains unchanged—for example, if the element was already placed there via margin-top and its layout position is the same as it would be with the clear effect (meaning the clear property doesn't occupy extra space, the so-called clearance)—then it does not have clearance. Conversely, if applying the clear property causes the actual position to change, meaning there's space above the element created by the clear property, then it is considered to have clearance.

Having clearance is not enough; the element's top and bottom margins must also be adjoining (meaning the element's actual height is 0 and it has no padding, border). If both are true, the margin collapse for this element is restricted: its margins only collapse with the adjoining margins of its immediate siblings, and the resulting margin will not collapse with the bottom margin of the parent block.

P.S. At this point, we have an entry ticket to challenge Classic Scenario 3, but there's still a long way to go.

The Definition of "Adjoining"

When are two margins considered "adjoining"?

  • Both belong to in-flow block-level boxes that participate in the same block formatting context.

  • No line boxes, clearance, padding, or borders separate them.

  • Both belong to vertically-adjacent box edges.

Three sentences, four new concepts. Let's go through them depth-first.

In-flow

In-flow/out-of-flow refers to whether the element is laid out using the normal flow positioning scheme.

Continuing depth-first, there are three types of positioning schemes:

  • Normal flow. Includes block formatting, inline formatting, and relative positioning.

  • Floats. Taken out of the normal flow and moved left/right.

  • Absolute positioning. Removed from the normal flow and positioned relative to its containing block.

If an element is not floated (the used value of the float property is none), is not absolutely positioned (the used value of the position property is not absolute), and is not the root element, then it is laid out according to the normal flow and is considered an in-flow element. Otherwise, it is an out-of-flow element.

Block Formatting Context (BFC)

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block. The vertical distance between two sibling boxes is determined by the 'margin' properties.

That is, if no one establishes a new BFC, everyone resides in the current BFC. Much like JS scopes, by default, everyone is in the outermost scope (outermost block formatting context). Encountering an ordinary block-level box puts it in the block formatting context; encountering a special one (floated, absolutely positioned, etc.) establishes a new layer of scope (a new block formatting context), and all elements inside it are placed into this inner scope (the new block formatting context).

After layout is complete, from the perspective of formatting contexts, it is a series of nested BFCs, each responsible for managing the layout of a group of block boxes (or block-level elements).

Note: I won't mention Inline Formatting Context (IFC) here because distinguishing between different IFCs doesn't have much significance (there are no special scenarios across IFCs in the specification definition). So, when is a new inline formatting context created? According to the spec, a new IFC is created only when a block container contains only inline-level boxes, unlike BFC which can be explicitly forced.

P.S. For more discussion on when a box establishes an IFC, see When does a box establish an inline formatting context?

Line Box

The rectangular area that contains the boxes that form a line is called a line box.

A line box is always tall enough for all of the boxes it contains.

A line box is CSS's abstract representation of a line; all elements in a line are within the same line box. If a line is too long and wraps, a new line box is created for the next line. On the other hand, a line box is not purely an abstract definition; it has width and height used to determine line layout.

"No line boxes" between adjoining margins can be simply understood as no inline elements separating them.

Vertically-adjacent Box Edges

The following four scenarios satisfy the condition where margins belong to vertically-adjacent box edges:

  • The top margin of a box and the top margin of its first in-flow child.

  • The bottom margin of a box and the top margin of its next in-flow sibling.

  • The bottom margin of a last in-flow child and its parent's bottom margin, if the parent's height computes to 'auto'.

  • The top and bottom margins of a box that does not establish a new block formatting context and has a computed 'min-height' of 0, a computed 'height' of 0 or 'auto', and no in-flow children.

This seems too long, so let's simplify the conditions assuming they are all in-flow elements:

  • Parent-child: Parent's top margin and the eldest child's top margin.

  • Siblings: An element's bottom margin and its following sibling's top margin.

  • Parent-child: The youngest child's bottom margin and the parent's bottom margin.

  • Self: The top and bottom margins of a zero-height "vacuum" element.

P.S. "Vacuum" here means—vacuum-sealing potato chips. Either there's nothing inside, or all in-flow children have been removed.

That is, the position definition of "adjoining margins" falls into three specific cases: parent-child, siblings, and self (collapse of an element's own top and bottom margins is quite peculiar).

Re-understanding "Adjoining" and Margin Collapse

With the previous conceptual groundwork, let's integrate these scattered points and redefine "adjoining":

Parent-child, siblings, or an element's own margins being flush against each other constitutes being "adjoining".

There's another key point: flush against. This means these two margins are not separated by a "wall". There are three types of "walls":

  • Race: Both must be in-flow block-level boxes.

  • Faith: Both must participate in the same block formatting context.

  • Geography: There are no line boxes, clearance, padding, or borders between them.

Now, "adjoining" is very clear. Let's backtrack to the definition of margin collapse:

Adjoining vertical margins of non-root elements collapse; if there is clearance, collapse is restricted.

Restricted means that if the top and bottom margins of an element with clearance are adjoining, they can only collapse with siblings' margins and cannot collapse with the parent's bottom margin.

3. Corollaries of Collapse Conditions

Based on the conditions for margin collapse, there are eight corollaries:

  • Margins between a floating box and any other box do not collapse (not even between a float and its in-flow children).

  • Margins of elements that establish new block formatting contexts (such as floats and elements with 'overflow' other than 'visible') do not collapse with their in-flow children.

  • Margins of absolutely positioned boxes do not collapse (not even with their in-flow children).

  • Margins of inline-block boxes do not collapse (not even with their in-flow children).

  • The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling, unless that sibling has clearance.

  • The top margin of an in-flow block-level element collapses with the top margin of its first in-flow block-level child, provided the element has no top border or top padding and the child does not have clearance.

  • The bottom margin of an in-flow block-level box with a 'height' of 'auto' and a 'min-height' of 0 collapses with the bottom margin of its last in-flow block-level child, provided the box has no bottom padding or bottom border and the child's bottom margin does not collapse with a top margin that has clearance.

  • A box's own margins also collapse, provided the 'min-height' property is 0, it has neither top/bottom borders nor top/bottom padding, the 'height' is 0 or 'auto', and it contains no line boxes; then all its in-flow children's margins (if any) will collapse.

Simplified summary in just 4 points:

  • Non-in-flow (absolutely positioned or floated) elements do not collapse.

  • Elements triggering a new BFC (floats, absolute elements, non-block-box block containers, and certain boxes with 'overflow' other than 'visible') do not collapse with children.

  • Non-block-level boxes (inline-block) do not collapse.

  • Generally, the bottom and top margins of siblings, the top and bottom margins of parents and children, and the top and bottom margins of an element itself will collapse.

The first three points address the prerequisites for "adjoining" (in-flow, same BFC, block-level box), and the fourth point is a rephrasing of the four "adjoining" scenarios, which expand into the eight corollaries.

4. Collapse Behavior

The margin formed after two adjoining margins collapse is called a collapsed margin.

P.S. "collapsed margin" is used here to denote the result, distinguishing it from the action of merging.

Margin collapse has two characteristics:

  • Recursive: i.e., deep collapse. After one collapse, check if there are other adjoining margins that can collapse with the result, and continue if so.

  • Greedy: Seeking the widest collapse result. For two positive margins, take the maximum; for two negative margins, take the maximum of their absolute values.

For the recursive characteristic, the definition of "adjoining" expands into a recursive formula:

A collapsed margin can also be adjoining to another margin if any of its component margins is adjoining to that margin.

Greediness relates to how the margin collapse result is calculated. Since margins allow negative values, it's slightly more complex:

  • If both are positive, take the maximum of the two.

  • One positive and one negative, add them together.

  • Both negative, take the maximum of their absolute values.

For example:

ul {margin-bottom: -15px;}
  /* 缩进表示对应文档结的构嵌套关系 */
  li {margin-bottom: 20px;}
h1 {margin-top: -18px;}

Then the vertical distance between h1 and the last li is 20 + -max(|-15|, |-18|) = 2px.

Whether for positive or negative values, the principle of taking the maximum is to make the collapse result as wide as possible (a negative value with a larger absolute value offsets the element content further), which is greediness.

5. Online Demo

Demo URL: http://ayqy.net/temp/margin-collapse.html

P.S. The answers are in the Demo, and the explanations are in the source code.

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment