Front-end developers with some experience will be familiar with the notion of “clearfixing”. Typically, we run across this problem early in our careers, learn the fix, then don’t think much about it again. But sometimes it’s useful to return to familiar things to deepen what we already know.
First, a quick reiteration of what “clearfix” is all about. It’s actually a small shame this is the common term we settled on: a more accurate term would be “float containment”, since it refers to techniques for forcing a block-level element to span the full vertical extent of – that is, contain – its floated children. The reason floated elements aren’t automatically contained is that, like positioned elements, floats are removed from the document flow – even though they still affect their immediate context. Because they’re removed from flow, the height of their parent is no longer constrained by the floated content. So, if there is insufficient other in-flow content to fill it, a floated element’s container may be shorter than the floated content.
For at least a little while longer, most of us will still be using floats for day-to-day layout, and we’ll routinely have elements with nothing but floated children. As a result, these containers will have no height at all, and margins, borders, padding and backgrounds may not behave as we want.
Enter “clearfix”
Historically, there are two main approaches to solving these issues. The first makes use of the clear
CSS property, which is fairly easy to both see and to understand. The other approach uses some technical aspects of the CSS layout model – specifically the Block Formatting Context.
Using clear
The simplest method simply adds a presentational element to the markup after any floated elements, then forces this element to render below any floats using clear: both;
, a CSS declaration that does exactly that: forces a block-level element to stack below any preceding floated elements. Here’s an example (using inline styles for clarity only):
I am floated
I am floated
If I gave my .container
in this example a background color, we’d see that the container doesn’t collapse to zero height anymore, instead containing all its children.
Using a Block Formatting Context
You may have heard of Block Formatting Contexts before but may not have explored them in depth. If you haven’t heard of them, or spent the time to grok how they work, the effort is well worth your while. Thierry Koblentz of Yahoo likens them to closures in JavaScript: a little tricky, but one of the keys to mastering the language (Thierry has an excellent article that explains the details).
In brief, a Block Formatting Context (BFC for short, or a “flow root” in CSS3 jargon) demarcates a region of the document in which block boxes are laid out. BFCs themselves are part of the surrounding document flow (just like normal block elements) but they “isolate” blocks from the outside context, hence their name. This isolation has the following consequences:
- BFCs contain the vertical margins of child elements. Normally, the margins of two adjacent elements will collapse (overlap), but not if they are in different Block Formatting Contexts.
- BFCs and floats can’t overlap. This means that (among a other things) BFCs will contain their floated children, since floats extending past the bottom edge of a containing BFC would constitute an overlap.
So, to contain floats, all we need is to make our container a Block Formatting Context: but how? There are a few ways (all explicitly defined in the spec) that we can do this:
- Float the element.
- Ensure the computed value of
overflow
is notvisible
; for example, setoverflow: hidden;
. - Set
display
toinline-block
,inline-table
,table-cell
ortable-caption
. - Set
position
to something other thanstatic
orrelative
(in practice,absolute
orfixed
).
Unfortunately, each has side-effects: overflow: scroll
may trigger scrollbars and hidden
will clip overflow (including focus rings and positioned children). The various values for display
each make restoring block-like behavior prone to problems: all of them cause the element to “shrink-wrap”, usually requiring width: 100%
, sometimes other fixes. Using table display values poses problems for fluid images sized with max-width: 100%
in some browsers (notably Firefox). The problems with table-caption
can’t be overcome at all, as far as I know.
The Modern Solution
The modern solution to these challenges uses a combination of both float clearing and BFCs, and was developed by Thierry Koblentz (no surprise) and improved on by Nicolas Gallagher of Normalize.css fame in the form of the “micro-clearfix hack”. A simplified version looks like this:
/* Contain floats. */
.cf:after {
content: ' ';
display: block;
clear: both;
}
To use it, we just apply class="cf"
to a container. The float containment works the same way as the traditional “clearing” approach, but avoids the presentational markup by using CSS generated content (:after
).
The results are good, although they differ slightly under certain circumstances to float containment with a Block Formatting Context. In the following example, the top margin of the container‘s first child collapses with the bottom margin of the container’s previous sibling.
If we want consistency with BFC behaviour or simply want to contain margins as well as floats, we can employ a slightly different version:
/* Contain floats *and margins*. */
.cfm:before,
.cfm:after {
content: ' ';
display: table;
}
.cfm:after {
clear: both;
}
This version adds a pseudo-element at the :before
position. This element is set to display: table, which has the effect of generating an empty BFC. The reason is that elements with table
display, whether actual nodes or not, must have at least one
table-row
and table-cell
. So any empty element set to display: table
will automatically generate an anonymous table-row, and within that, an anonymous table-cell. Since table cells create new Block Formatting Contexts, an empty BFC is created at the top of the container. Since margins can’t collapse across the boundaries of a BFC, they are forced apart.
container
child
I am a block-level element.
container.cfm
.cfm:before
.cfm:after
child
I am a block-level element.
child.float
I am a floated element with float and margin containment.
Figure 5: Float containment using the full micro clearfix, showing that both margins and floats are contained.
Wrap-up
Often there’s more sophistication behind our “simple” day-to-day techniques than we remember or realize. Taking the time to reflect on and study them can give us a deeper understanding of why they work, giving us a better understanding of how browsers operate, what our specs say, and enable us apply the underlying principles beyond the hacks we’ve memorized. Here’s to the folks who came up with those hacks in the first place, and to everyone curious enough to poke around and invent the next little mundane tool we’ll use every day.