Why Is This Non-Float Margin Collapsing with a Float

Why is this non-float margin collapsing with a float?

Never mind, I think I found it myself. Looks like the following assumption in my question was wrong (told you I don't fully grok the CSS float model):

The fact that the clearing element itself is not floated shouldn't be relevant.

In section 9.5.2, which describes the clear property, it says:

Computing the clearance of an element on which 'clear' is set is done by first determining the hypothetical position of the element's top border edge. This position is where the actual top border edge would have been if the element's 'clear' property had been 'none'.

If this hypothetical position of the element's top border edge is not past the relevant floats, then clearance is introduced, and margins collapse according to the rules in 8.3.1.

Then the amount of clearance is set to the greater of:

  1. The amount necessary to place the border edge of the block even with the bottom outer edge of the lowest float that is to be cleared.
  2. The amount necessary to place the top border edge of the block at its hypothetical position.

And further down that section, it says:

When the property is set on floating elements, it results in a modification of the rules for positioning the float. An extra constraint (#10) is added:

  • The top outer edge of the float must be below the bottom outer edge of all earlier left-floating boxes (in the case of 'clear: left'), or all earlier right-floating boxes (in the case of 'clear: right'), or both ('clear: both').

(Emphasis mine. Note that "outer edge" is synonymous with "margin edge", as described in section 8.1.)

Essentially, this means if the clearing element is not floated and its top margin alone does not push it far enough away from the floats, then the element is pushed just enough for its top border to sit right underneath the bottom margin of the float being cleared. While it would appear as if its top margin was collapsing with the bottom margin of the float, in reality the top margin doesn't have any meaningful effect because it doesn't reach the border edge of the clearing element.

Otherwise if the clearing element is floated, then its top margin is taken into account, so to speak (as consistent with the rules stated in 8.3.1).

And as I write this, I recall that in-flow non-floating elements are positioned as if the floats were never there to begin with, because floats are taken out of the normal flow. In other words, any top margin that is set on a non-floating clearing element does not take into account any floats, regardless of whether it is enough for clearance. For example, when both clear and float are set to none on the last element, it sits flush with the edges of its container in the exact same position as the first floating element, albeit behind it (note that the borders on the container block margin collapse between it and the container).

Lastly, the fact that clearance is introduced is actually not relevant in this specific situation, because clearance blocks margin collapse only when the element's margins would normally have collapsed had its clear property been set to none. Since we're talking about floats here, margin collapse should indeed never happen normally, and so whether or not the last element has clearance isn't relevant (not directly, anyway).

Margin collapsing with floated element, why there is an extra margin added?

Before I start, the issue of scrollbars being rendered in all browsers but Firefox is a separate issue from what is being asked about here. Firefox does not collapse margins between a parent element and its children when the parent's min-height results in the margins not being adjoining. It's also a known spec violation in Firefox that's being worked on and yet to be fixed.

Now, on to the issue at hand. From section 9.5.1 (on floats):


  1. A floating box's outer top may not be higher than the top of its containing block. When the float occurs between two collapsing margins, the float is positioned as if it had an otherwise empty anonymous block parent taking part in the flow. The position of such a parent is defined by the rules in the section on margin collapsing.

The last sentence in this quote is awkward, but "the rules" refer (and link) to the definition of collapsing through. While the specific text that you cite from that section is relevant, it doesn't explain why the floats respect the margin of the in-flow div.

This does:

If the top and bottom margins of a box are adjoining, then it is possible for margins to collapse through it. In this case, the position of the element depends on its relationship with the other elements whose margins are being collapsed.

  • [...]

  • Otherwise, either the element's parent is not taking part in the margin collapsing, or only the parent's bottom margin is involved. The position of the element's top border edge is the same as it would have been if the element had a non-zero bottom border.

Note the last sentence. Having a non-zero bottom border cancels margin collapsing, as you know. This means that the floats are positioned as if the bottom margins of the in-flow div and the body element did not collapse, resulting in the floats appearing to respect the bottom margin of the in-flow div.

How do I tell that the floats specifically respect the bottom margin of the in-flow div and not the collapsed margin? By giving body a larger bottom margin than that of the in-flow div and observing that it does not affect the position of the floats:

html {
background: red;
}

body {
margin: 0;
margin-bottom: 100px;
min-height: 100vh;
background-color: green;
}

div {
min-height: 50px;
background-color: pink;
margin-bottom: 50px;
}
.l {
width:45%;
height:50px;
float:left;
margin:0;
}
.r {
width:45%;
height:50px;
float:right;
margin:0;
}
<div></div>
<div class="l"></div>
<div class="r"></div>

Margin collapsing with floated elements

In neither IE6 nor the standards browsers are the float's margins collapsing with its siblings. This is correct as per the quoted standard.

The difference in renderings is caused by IE6's interpretation of which margins flow together. (If you give each div a background colour it makes it easier to see what is happening here.)

Two or more adjoining vertical margins of block boxes in the normal flow collapse

With the normal flow defined thus:

Since a float is not in the flow, non-positioned block boxes created before and after the float box flow vertically as if the float didn't exist.

That is, they flow together and have adjoining vertical margins which may collapse. IE6 (and IE7 in Quirks Mode) gets this wrong, and thinks that the float breaks up the flow, causing no collapsing to occur.

With the general confusion surrounding vertical margins and collapsing, compounded by the still-extant browser bugs, I'd recommend avoiding vertical margins and using padding instead wherever possible.

margin issue on non-floated elements

Check Demo

CSS Margin Collapsing

float:left; or display: inline-block solves the above issue

Let’s explore exactly what the consequences of collapsing margins are, and how they will affect elements on the page.

The W3C specification defines collapsing margins as follows:

In this specification, the expression collapsing margins means that
adjoining margins (no non-empty content, padding, or border areas, or
clearance separate them) of two or more boxes (which may be next to
one another or nested) combine to form a single margin.

In simple terms, this definition indicates that when the vertical margins of two elements are touching, only the margin of the element with the largest margin value will be honored, while the margin of the element with the smaller margin value will be collapsed to zero.1 In the case where one element has a negative margin, the margin values are added together to determine the final value. If both are negative, the greater negative value is used. This definition applies to adjacent elements and nested elements.

There are other situations where elements do not have their margins collapsed:

  1. floated elements
  2. absolutely positioned elements
  3. inline-block elements
  4. elements with overflow set to anything other than visible (They do
    not collapse margins with their children.)
  5. cleared elements (They do not collapse their top margins with their
    parent block’s bottom margin.) the root element

This is a difficult concept to grasp, so let’s dive into some examples.

how `clear` prevent margin collapsing?

This ensures that a clear prevents any following elements from overlapping the floats.

Let's start with floats and clear for now. Floats can overflow their parent:

<div style='border:1px solid green;'>
<div style='float:left;background:red;height:100px;width:40px;'></div>
That red box overflows!
</div>

If we add a clearing div, it never will. A clear is like saying nothing else can flow above this line:

<div style='border:1px solid green;'>
<div style='float:left;background:red;height:100px;width:40px;'></div>
<div style='clear:both;'></div>
<!-- Anything down here will not overlap the floats -->
</div>

However, margin collapsing breaks things a little, because following elements can collapse 'through' something, going all the way up to the very top of the top margin. Let's have a quick excursion into certain aspects of margin collapsing.

Self collapsing hacks

In general, margin collapsing applies to any top margin which is directly touching any bottom margin.

That includes an elements own top/bottom margins too. This is called self-collapsing, and margin collapsing happens repeatedly. Here's a quick example of both of these things together:

<div style='margin-top:30px; margin-bottom:30px;'></div>
<div style='margin-top:30px; border:1px solid black;'>
The gap above me is only 30px, not 90!
</div>

That first div entirely self-collapses, resulting in a computed space of 30px, then the second div collapses into that too, keeping the space at just 30px.

Ok, so we've now got a rough idea of what self-collapsing is. Now let's start trying to abuse that with a self-collapsing clearing div:

<div style='border:1px solid green;'>
<div style='float:left;background:red;height:100px;width:40px;'></div>
<div style='clear:left;margin-top:90px;margin-bottom:90px;'></div>
I'm after the clear and look, no 90px gap!
</div>

The margin is still there though. It actually runs 90px upwards over the floats.

Next, imagine there was no text after it, and the parent had a bottom margin. By our margin collapsing rules, it should collapse upwards. Sibling elements might even collapse 'through' it, all the way up to the top. We don't want that, because it would cause some unwanted overlapping.

This part of the specification blocks this behaviour. Let's break the spec's language down to make that clearer:

If the top and bottom margins of an element with clearance are adjoining

This is describing a self-collapsing element which has cleared a float.

its margins collapse with the adjoining margins of following siblings

It's fine for other margins to collapse into it, but..

That resulting margin does not collapse with the bottom margin of the parent block.

..the very bottom margin must not collapse upwards, because that would result in our awkward overlapping case.

Here's an example of the case where the rule is applied:

<div style='border:1px solid green;'>
<!-- A parent block with a bottom margin, and no border -->
<div style='margin-bottom:50px;'>
<div style='float:left;background:red;height:100px;width:40px;'></div>
<!-- A self collapsing cleared div -->
<div style='clear:left;margin-top:50px;margin-bottom:50px;'></div>
<!-- The parents bottom margin is adjacent to our collapsed margin,
but it gets blocked from collapsing upwards. We see a gap here -->
</div>
</div>

Adding some text into that clearing div makes it no longer self collapse, but its bottom margin then safely collapses with the bottom margin of the parent instead.

Why do non-floating parents of floating elements collapse?

Take a look at this section in the w3c CSS 2.1 spec: 9.5 Floats

Note this image at the bottom, just before section 3.5.1:

image protruding outside containing paragraph, while text in following paragraph wraps it.

... this seems to provide the use-case I'm after:

Case description: You have an image floated inside a paragraph with a great enough height that it extends well below its sibling text. In general, you'd want the text in the subsequent paragraph to wrap around this image as well. The way to achieve this is to allow the image to protrude outside the containing paragraph. Otherwise -- if the first, containing paragraph's height extends all the way down to wrap the image -- the subsequent paragraph gets pushed down completely below the image, leaving a large white-space between the texts of the two paragraphs.

However: more often than not we don't want this effect when using floats. So often we need floats to layout main areas of a web page (lest we resort to tables), and in these cases we typically need a container to expand to include whatever is inside in its height calculation.

My wish: It seems, then, that there should be a CSS property along the lines of:

box-model-height: [ include-floats | exclude-floats ];

Browsers could have the default on paragraphs as "exclude-floats", and all other blocks as "include-floats".

Or if that would break too many designs, "exclude-floats" could be the default everywhere, and we could still fix the situation, wherever we need to, entirely in the style-sheet, instead of requiring a class-name (e.g. clearfix) in the markup.



Related Topics



Leave a reply



Submit