CSS 2.1 Spec: Rationale for Not Collapsing Margins of Parent (When Parent Is Float or Has Overflow Other Than Visible)

The impact of 'overflow' values other than 'visible' on the block formatting context

As I have covered in my answer to the question that you link to, the main reason overflow values other than visible result in a new block formatting context is due to implementation limitations relating to floats, even though the concept of overflow does not immediately appear to have a relationship with floats.

While the relationship between floats and collapsing margins is pretty simple (it never occurs), the fact that margins cannot collapse through the boundaries of an element with such a value for overflow either is little more than a side effect of this change, because margins are defined not to collapse through any box that establishes a block formatting context, as described in section 8.3.1. I quote:

  • 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.

This includes both floats and elements with such a value for overflow. The overflow itself does not actually have any direct effect on the margins.

When both the parent and the child are block-level elements that participate in the same block formatting context, they will collapse by default unless there is something in the way:

  • The top margin of an in-flow block element collapses with its first in-flow block-level child's top margin if the element has no top border, no top padding, and the child has no clearance.

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

This explains why the parent's background does not extend until you try and block the margin collapse.

Why are margins not collapsing?

Flex items' margins won't collapse. When flex items wrap, they create their own row, and the individual margins on the flex items won't collapse between rows. Only normal, adjacent block elements stacked will margin collapse the way you're intending. This is a good reference - https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing

You can create the same layout by removing the top margin from the li's and making that a padding-top on the ul instead, then only the bottom margin will be applied between li's when the flex row wraps.

ul {  list-style-type: none;  padding-left: 0px;}
a { text-decoration: none;}
main { background-color: #F1F4F5; padding: 30px 0px;}
.main-wrapper { max-width: 800px; margin: auto;}
.tags ul { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; margin: 0; padding-top: 20px;}.tags li { margin: 0 10px 20px; display: block;}.tags a { display: block; justify-content: center; color: #3A4250; background-color: #e6e6e6; border-radius: 20px; padding: 7px 20px; transition: background-color 0.3s ease, color 0.3s ease;}.tags a:hover { color: #FFFFFF; background-color: #FC575E; transition: background-color 0.3s ease, color 0.3s ease;}
<main>  <div class="main-wrapper">    <div class="tags">      <ul>        <li>          <a href="#">ELEMENT 1</a>        </li>        <li>          <a href="#">ELEMENT 2</a>        </li>        <li>          <a href="#">ELEMENT 3</a>        </li>        <li>          <a href="#">ELEMENT 4</a>        </li>        <li>          <a href="#">ELEMENT 5</a>        </li>      </ul>    </div>  </div></main>

Why does CSS2.1 define overflow values other than visible to establish a new block formatting context?

I asked about this on the mailing list on your behalf; the thread can be found here. In summary, this has to do with scrolling content for the most part:

Fundamentally, because if the spec didn't say this, then having floats intersect with something that's scrollable would require the browser to rewrap (around intruding floats) the contents of the scrollable element every time it scrolls. This is technically what
CSS 2.0 required, but it was never implemented, and it would have been a huge problem for speed of scrolling.

-David

Most likely, it refers to scrollable content in a box that may occur outside of the float's parent but would intersect with the float. I don't think this is related to rewrapping content around a float within a scrollable container, as that already happens naturally, plus the float would clip into the container and scroll along with the rest of its content anyway.

Finally this makes sense to me. In fact, I'm going to provide an example here so hopefully it makes sense to you and anyone else who may be wondering. Consider a scenario involving two boxes with the same fixed height and overflow: visible (the default), of which the first contains a float that stretches beyond its parent's height:

<div>
<p>...</p>
</div>
<div>
<p>...</p>
<p>...</p>
</div>
/* Presentational properties omitted */
div {
height: 80px;
}

div:first-child:before {
float: left;
height: 100px;
margin: 10px;
content: 'Float';
}

Sample Image

Notice the similarity to one of the examples given in section 9.5. The second box here is simply shown to have overflowing content for the purposes of this answer.

This is fine since the content will never be scrolled, but when overflow is set to something other than visible, that causes the content to not only be clipped by the bounds of the box, but also to become scrollable. If the second box has overflow: auto, this is what it would look like had a browser implemented the original CSS2 spec:

Sample Image

Because of the float, attempting to scroll the content would cause the browser to have to rewrap it so it doesn't become obscured by the float (and what should happen to the part that scrolls out of the top edge?). It would probably look something like this when scrolled to the bottom:

Sample Image

The catch here is that the browser has to rewrap the content every time it repaints it during scrolling. For browsers that are capable of pixel-based smooth scrolling — which is to say, all of them — I can see why it would be a performance disaster! (And a user experience one, too.)

But that's for when the user can scroll the content, right? This would make sense for overflow: auto and overflow: scroll, but what about overflow: hidden?

Well, a common misconception is that a container with overflow: hidden simply hides content by clipping and cannot be scrolled. This is not completely true:

While scrolling UI is not provided, the content is still scrollable programmatically, and a number of pages perform just such scrolling (e.g. by setting scrollTop on the relevant element).

-Boris

Indeed, this is what it'd look like if the second box was set to overflow: hidden and then scrolled to the bottom with the following JavaScript:

var div = document.getElementsByTagName('div')[1];
div.scrollTop = div.scrollHeight;

Sample Image

Again, notice that the content would have to be rewrapped to avoid being obscured by the float.

Even though this wouldn't be as painful for performance as had scrolling UI been available, my best guess is that they made boxes with any overflow value other than visible generate a new BFC mainly for the sake of consistency.


And so, this change was brought about in CSS2.1, documented here. Now if you apply an overflow value other than visible only to the second box, what a browser does is push the entire box aside to make way for the float, because the box now creates a new block formatting context that encloses its contents, instead of flowing around the float. This particular behavior is specified in the following paragraph:

The border box of a table, a block-level replaced element, or an element in the normal flow that establishes a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the margin box of any floats in the same block formatting context as the element itself. If necessary, implementations should clear the said element by placing it below any preceding floats, but may place it adjacent to such floats if there is sufficient space. They may even make the border box of said element narrower than defined by section 10.3.3. CSS2 does not define when a UA may put said element next to the float or by how much said element may become narrower.

Here's what it looks like with overflow: auto for example:

Sample Image

Note that there is no clearance; if the second box had clear: left or clear: both it would be pushed down, not to the side, regardless of whether it established its own BFC.

If you apply overflow: auto to the first box instead, the float is clipped into its containing box with the rest of the content due to its fixed height, which is set to 80px in the example code given above:

Sample Image

If you revert the first box to height: auto (the default value), either by overriding or removing the height: 80px declaration from above, it then stretches to the height of the float:

Sample Image

This happens to be new in CSS2.1 as well, in that an element with height: auto that generates a new block formatting context (i.e. a block formatting context root) will stretch vertically to the height of its floats, and not just enough to contain its in-flow content unlike a regular box. The changes are documented here and here. The change leading to the side-effect of shrinking the box so that it does not intersect the float is documented here.

In both of these cases, no matter what you do to the second box, it will never be affected by the float because it has been restricted by the bounds of its container.

Vertical margins disappear when parent is set to overflow:visible

It's because of collapsing margins:

If you have overflow: hidden, the margins of the inner div remain inside the outer div.

If you have overflow: visible, the top and bottom margins collpase with the zero margins of the outer div, because they touch each other. This is then recalculated to have the same as the inner margin.

So, overflow: hidden will break collapsing margins with the ones inside. You could break the margin collapsing by giving the outer div a padding or a border. So they won't touch each other and so no collapsing

http://www.howtocreate.co.uk/tutorials/css/margincollapsing

Is there any way to allow CSS margins to collapse through a fieldset boundary?

Yes, the fieldset element establishes the new block formatting context (this behavior was first implemented in the browsers, so the spec incorporated this feature as part of "expected default rendering").

Unfortunately, I don't know any way to "undo" this with CSS, except completely removing the fieldset element's box by setting it to display:contents, which currently only gives the desired result in Chrome with the "Experimental Web Platform features" flag turned on (Firefox, although implemented display:contents back in 2015, hasn't updated its implementation to work for "unusual elements" like form controls according to the recent addition to the spec yet).



Related Topics



Leave a reply



Submit