Friday, May 8, 2009

Clearing floats without overflow hidden

Clearing floats is a topic near and dear to web developers fighting with CSS to produce grid layouts. There are a number of methods to do this, but not all work well in all situations. I wanted to mention one I'm using at work at Google that's gotten pretty good mileage so far in ways that others have not.

Our grid system is very similar to Yahoo's - it's designed to nest two units inside of a container and then further nesting takes place in one of the two units. Our system can scale to infinite nesting in browsers that support first-child selectors, and for those that don't we bake a nasty mix of descendent selectors to achieve a baseline of 4 levels of nesting (which seems pretty sufficient for most projects).

A fixed layout of 180-px on the left and fluid on the right looks like this in HTML:

<div class="g-section g-tpl-180">
<div class="g-unit g-first">
<div class="box-1">1</div>
</div>
<div class="g-unit">
<div class="box-2">2</div>
</div>
</div>


This grid/horizontal column layout is done in CSS by making the g-first element float:left and then have a fixed width:180px then the second g-unit float:none and margin-left:180px. So, since something is floating here, we want our g-section to contain it.

For containing floats, here's what I've found:
* Clearfix is nice, but doesn't work when nesting sections within units, so for our framework it is useless.
* Faux Absolute Positioning is pretty cool, but doesn't mix well on pages that don't adopt this approach whole hog (i.e. everything has to be inside of a div with class="line". So that's out.
* overflow:hidden - Overflow hidden for a CSS framework is like using crack - it's solves the immediate problem nicely, but as things build up and edge cases kick in, it's unsustainable and gross. Hiding parts of the UI and not providing users with a scroll bar is just plain wrong, even if it makes your design "look" good still. All sorts of things in Web Apps will get you into this situation - dynamic-width/column data tables, form input elements, images, etc.. Cutting them off and asking your user to get a bigger screen is wretched.

I'm going to paste the code and comments I'm currently using for g-section below. Hopefully it will be of use to someone stumbling across this. I have unit tests that test against float-drops and sizing of the template is all the basic cases as well as cases where the sections are nested up to 4 in depth as well as nested inside of floats. This last case is one where I'm noticing some issues related to how width of the outer float container gets calculated - but that's a subject of a whole other post.

Update May 10: So it seems that the fix for a pure float-left container is to set width to be auto for the outer container and then display: inline for nested g-section's. If that makes sense then you'll be happy to hear it solves the problem of width aggregation for the inline-blocks!

I welcome your suggestions - thanks!


/**
* g-section fundamentally has to clear floats. There are many ways to do this.
* This technique is nice because it doesn't rely on overflow: hidden, which
* has the potential to hide your content in situations where a fixed size
* node takes up too much space (like a big table, or a text input or image.
* Works in Webkit, IE8, and FF3.
*/
.g-section {
width: 100%;
vertical-align: top;
display: inline-block;
}

/**
* IE7-only hack. Nicely IE7 will clear floats with just block display
* and hasLayout.
*/
*:first-child+html .g-section {
display: block;
}

/**
* IE6 cannot hang with overflow: visible. If we use the IE7 display block
* trick in IE6 we get severe float drop in nested grids.
*/
* html .g-section {
overflow: hidden;
}

/* FF2 can't actually hang with overflow: visible. */
@-moz-document url-prefix() {
.g-section {
overflow: hidden;
}
}

/**
* FF3 now needs to be reset after the previous block which affects it as well.
* We target the tt element in this hack because no one uses it.
*/
@-moz-document url-prefix() {
.g-section,tt:default {
overflow: visible;
}
}

/* Forces "hasLayout" fixing a gamut of bugs in <= IE7. */
.g-section,
.g-unit {
zoom: 1;
}

5 comments:

Thierry said...

I don't think any of these new-block formatting context triggers is a magic bullet as each one comes with its own baggage.

With inline-block you end up using "width" :-(

As a side note, MS came up with the best tool for this job: hasLayout

Ernest said...

Agreed with Thierry. For instance, inline-block does not work to contain floats in widthless containers.

elsigh said...

Yeah, no magic bullets for sure. All of these approaches seem to have their own sucky drawbacks.
@Thierry: I've never seen anyone defend hasLayout! That's curious, and I wonder if you might have more to say about it.
@Ernest: sure, that's why in my example I had to set the width to 100%, but for a grid container, that's an ok thing to do.

Thierry said...

hasLayout is enough to contain float and it works on widthless containers.
There is absolutely no string attached with hasLayout. Which is not the case with the "official" new block-formatting context triggers.

Ernest said...

The g-section at the end doesn't need the zoom: 1 declaration since the element already has a layout because of the 'width: 100%' in the first rule.