January 20, 2014

Understanding the Humble Clearfix

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.

container
child.float
I am a floated element without any containment.
Figure 1: A container with a single floated child element. Because of the way floats behave, the container will have zero height.

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:

  1. 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.
  2. 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:

  1. Float the element.
  2. Ensure the computed value of overflow is not visible; for example, set overflow: hidden;.
  3. Set display to inline-block, inline-table, table-cell or table-caption.
  4. Set position to something other than static or relative (in practice, absolute or fixed).

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.

container.bfc
child.float
I am a floated element whose parent creates a new block formatting context.
Figure 2: Float containment by creating a Block Formatting Context. In this case, the BFC is created by floating the container itself and assigning a 100% width.

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

container.cf
.cf:after
child.float
I am a floated element whose parent uses generated content to contain floats.
Figure 3: Float containment using the simple micro clearfix (generated content).

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.

container
child
I am a block-level element.
container.cf
.cf:after
collapsed margins
child
I am a block-level element.
child.float
I am a floated element whose parent uses generated content to contain floats; however, top margins are not contained.
Figure 4: Float containment using the simple micro clearfix, showing top margin collapse scenario.

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.