Pavel Panchekha

By

Share under CC-BY-SA.

How CSS Floats Work

In Cassius, I am formalizing the CSS standard; in other words, I am implementing my own web browser, in logic. I've been reading the standard, trying out examples in browsers, and condensing the standard into something easy for a computer to reason about. Recently I've been working on floats, and I've come up with a quite simple mental model for thinking about float layout. Since I don't see it described anywhere else, I want to explain.

Table of Contents

Summary

In CSS, you can set the float property on something to make it move out of its normal position to the left or right, and to make text wrap around it. The representative use for float is to make an figure float besides text. However, it was also the only mechanism in early CSS to stack things horizontally. It is still the most common and best-supported way to create grids and horizontal stacks, and it's also used for pull quotes, margin notes, sidebars, and column layouts.1 [1 For example the two-column layout on my homepage uses floats.]

I wrote this page for web developers who want a deep understanding of floats. This page won't be maximally rigorous, but it will be a much deeper look at floats than you'll find elsewhere.

Note that understanding floats deeply doesn't mean using them often. In fact, I would recommend using absolute position, flex-box, and grid layout over floats, when possible. I'd use floats only when text needs to wrap around the thing being floated. Furthermore, I think sticking to the following rules is best:

  • Always give a fixed width to floating boxes.
  • If you are mixing left and right floats, have them far enough apart that they don't interact
  • Prefer interacting floats to have the same height

For when you can't keep to these rules, you'll need the larger mental model described below.

How are floats positioned

The confusing thing about floats is why they end up in the place they do. When there's a single float, it's easy, but how do you explain things like this page, where every box floats and the numbers are their order in the HTML?

The basic rule for floats is best described by MDN:

It is shifted to the left or right until it touches the edge of its containing box or another floated element.

However, the full rules2 [2 Floats were introduced in version 2, and have stayed largely identical. I am working off of the CSS 2.1 rules, which is what Cassius implements, but have glanced over the rules in CSS 2.2 and in CSS 3 and found them similar.] have a lot of subtleties: what happens if a left and a right float bump into each other? when floats of different sizes collide? I hope to explain.

Before diving into floats, I'm going to describe some preliminaries about browser layout in general, and then move on to positioning floats. As I explain, keep in mind that I'm describing a mental model for what a browser could do. Browsers are much more complicated than anything I say, but if you think of the browser the way I describe it, you'll come to the right conclusions.

CSS preliminaries: boxes, flows, and layout order

When you use CSS, you start with some HTML and some CSS.3 [3 You can also use CSS to style XML, SVG, and god knows what else, but let's stick to the simple cases.] The HTML defines a tree called an element tree. To turn this element tree into a picture, your browser proceeds in several stages: it generates boxes, links flows, and then computes dimensions and positions. The end result of this process is a set of rectangles4 [4 Of a few different sorts: text, images, backgrounds, borders, and so on.] to draw on the screen. Let me discuss each phase briefly.

Generating boxes: a box is the atomic particle of the rendering algorithm. Most boxes correspond to an HTML element, but the match isn't perfect. Some elements have display set to none, and do not generate boxes.5 [5 Its lesser-known cousin visibility: hidden generates a box but does not draw it.] Other elements generate multiple boxes, such as bold text split across two lines. Finally, text boxes6 [6 Generated by text nodes in the HTML, not by elements.] and line boxes7 [7 Not generated by anything in the element tree.] aren't generated by any elements at all. In a few weird cases, like a <div> placed inside a <b>, the box tree might not correspond to the element tree in any simple way.8 [8 In that case, the <div> will become a sibling of two separate <b> boxes—the part of the <b> before the <div>, and the part after—because a block box cannot be contained in an inline box. Here, a parent-child relationship between two elements becomes a sibling relationship between three boxes.] But roughly speaking, you can think of a box as corresponding to some bit of HTML. Boxes come in various types, most importantly the block box (corresponding to containers for text) and the inline box (corresponding to styled text).

Linking flows: The web wasn't originally about apps—it was about text documents, and the design of CSS reflects this. The most important way of laying out boxes is normal flow, used for text and other document-like things. Boxes that aren't in normal flow, like positioned or floating boxes, don't interact much with boxes in normal flow, and are treated like an exception to the textual norm. Boxes in normal flow are linked into flows. Each flow is a tree whose flow root is the root element of the page, or a box not in normal flow. Within a flow tree, we can talk about the next or previous box, and when we do that, we mean that we skip any siblings which aren't in that flow.9 [9 Because, say, they float so are roots of their own flow.]

Computing dimensions: With flows linked, the dimensions of each box—width, height, margins, borders, and paddings—and its position on the page can be computed. Roughly speaking, we think of the browser as traversing flow trees from the top down, computing horizontal attributes along the way, then computing vertical attributes from the bottom up, and finally doing an in-order pass to compute positions.

The asymmetry between width and height is because we need the width to know how to break lines of text, and we need to know how the lines of text broke to compute the height. The simple down-up picture is not quite correct. It is ruined by concepts like the shrink-to-fit width. However, it is a handy guide.

Since dimensions are computed using an top-down and bottom-up traversal of flow trees, a box's height generally shouldn't depend on the height of its parent, and its width generally shouldn't depend on the width of its children.10 [10 CSS allows both of these cases, with percentage heights and shrink-to-fit widths. Predictably, both concepts create referential loops. For example, a box might have a percentage height, so it is dependent on its parent's height, but that parent needs the child's height to compute its own height. In these cases, CSS specifies some “fallthrough” value, such as a height of 0 for the child.] Since positions are computed with an in-order traversal, a box's position shouldn't depend on boxes that come after it.

Drawing rectangles: Once every box's position and dimensions are known, it can be turned into a collection of rectangles, which are then drawn, in an order defined by the boxes' z-index property. The result is a picture that is shown to the user.11 [11 Ignoring the infinite complexities of images, video, animations, OS components like buttons, and so on.]

Where floats fit into this

Floats fit into the picture I just sketched in a few ways. First, we must discuss how they are linked into flows. Then, we must discuss how their dimensions are computed. Finally, we must discuss how they are positioned.

All boxes that float are flow roots. They leave the flow they would have been in had they not floated, and instead form their own, independent flow for their children. This means that a float's dimensions can be computed independently of its surroundings.

The dimensions of a float are mostly computed just like the dimensions of any other box, with a few twists:

  • A float's margins do not collapse with those of its children, and auto margins are always zero margins.
  • If a float's width property is auto, the box shrinks to the minimum width viable for its children, called the “shrink-to-fit” width.
  • If a float's height property is set to auto, that height includes all floating descendants.

I won't be describing margin collapsing, shrink-to-fit, or auto heights here. I would recommend setting a fixed width for floats, to avoid having to think about shrink-to-fit calculations, though shrink-to-fit is usually unsurprising. The way heights and margins are computed for floating boxes tends to be unsurprising as well.

With flows linked and dimensions computed, let's move on to positioning floats.

The exclusion zone

The rules of floats are laid out in CSS 2.1, §9.5.1, but they are well summarized by MDN:

It is shifted to the left or right until it touches the edge of its containing box or another floated element.

But what do we mean by shifting—what notion of “time” are we discussing—and what happens in the many edge cases, where left and right floats may interact, or when a float may not fit between the floats around it?

In this section, I will introduce the idea of an exclusion zone, which is a simple model for answering all of these complex questions.

Let's start with what happens when there is only one float in a document, and nothing exceptional occurs. For example, consider the layout in the first picture here:

9.svg

In this picture and every future one where every box is drawn in black, I am drawing the final rendered result of a web page. Every rectangle corresponds to a box; line boxes are the ones with squiggles inside, and block boxes are the others. Every box is drawn at a slight offset to make the rendering easier to understand. In a real rendering, for example, a line box's bottom edge would be identical with the top edge of the next line box.

Let's look at how the big box, a left float, is positioned. The state of things when we're positioning it is shown in the second image above. The top edge is placed at the “current position” in the normal flow of its parent. I've drawn that y-position with a red line, which I call the high-water mark. The left float is placed so that its left edge is the left edge of its parent.12 [12 A right float would be placed at the right edge. In general, left and right floats behave totally symmetrically, so I will only discuss left floats here.]

In this and the other pictures in this document, the red line is the high-water mark, the blue boxes are block boxes, and the green boxes are line boxes. The box we are currently positioning is drawn dashed, while boxes not yet positioned aren't drawn at all, the browser doesn't yet know where they go.

As the browser positions boxes, it must keep track of the high-water mark, both to position floats and also to position boxes in normal flow.

As soon as we have more than one float, this picture becomes more complicated. Consider the rendering below:

10.svg

Here we have two floated boxes (highlighted). The first is laid out just like before, but the second does not have its left edge at its parent's left edge. This is because previous floats create a region of the page where other floats aren't allowed. I call this region the exclusion zone. Tracking and updating the exclusion zone is how a browser positions floating boxes. In the image above, I've drawn the exclusion zone after the current float is positioned with a dashed red line.

Or consider the following rendering process:

11.svg

Here, the first two floats are laid out uneventfully. Since one float is a left-float and the other is a right-float, the produce a doorway-shaped exclusion zone. The third float does not fit in the doorway shape, so is placed further down. Again, the shape of the exclusion zone describes where floats may be placed.

More generally, the exclusion zone is a region where no floats can ever go, no matter what sorts of boxes we will yet see. As we place new floats, we update the exclusion zone. The shape of the exclusion zone is defined by the rules in §9.5.1; it can have following shapes:

3a.svg

but it cannot have the following shapes:

3b.svg

In general, the exclusion zone is described by the high-water mark described above, plus two staircases, one on the left and one on the right.

We can describe the shape of the exclusion zone with the following data:

  • The y-position of the high-water mark
  • A x- and y-position of steps on the left
  • A x- and y-position of steps on the right

I've summarized this data in the following image:

4.svg

If you sort the steps by y-position, the left-hand steps decrease in x-position, while the right-hand steps increase in x-position. Each step’s y-position is greater than the y-position of the high-water mark. As we're about to see, each step is in fact the corner of some floating box.

You may be wondering about the last “impossible” exclusion zone I described. Could you have a layout like this?

3c.svg

You could, but at no point during the positioning phase would the exclusion zone look like the impossible one. You can see this with the second and third picture here.

Positioning floats

Now that we understand what exclusion zones look like, we need to describe how the browser:

  • Positions a float, given the exclusion zone
  • Updates the exclusion zone after having positioned a float

I'll focus on left-floats. Right-floats are the same, except that left and right is flipped.

6.svg

Normally, a left-float is positioned with its top at the high-water mark and its left edge at the largest x-position of the left steps, as in the first image above. However, if there is not enough room for the float there, either because the float would overlap the right edge or because it would overlap the right-hand part of the exclusion zone, the float moves down until it finds a y-position where it can fit. If there's no such position, it moves past every step and then stops.

This is the answer to the first question above. A left float is placed by finding the smallest y-position that is below the high-water mark and where the float fits between the left and right steps (or if there isn't such a y-position, past every step).

Now we must discuss how to update the exclusion zone. The algorithm is simple: the bottom-right corner of our float will be a new left-hand step, and the top of our float will be the new high-water mark. After these changes, some previous steps might above the high-water mark, or might be both above and to the left of the corner. We remove those steps.

There is a weird special case: what if a float's bottom-right corner is right along the edge of an exclusion zone, like in the first of these images:

8.svg

Should we update the high-water mark to be the bottom of the float, or should we leave a zero-pixel region between the left and right edges of the exclusion zone?

In fact, you leave a zero-pixel region, because the exclusion zone does not know about the parent box's edges (that's why I've drawn it going past the edges of the parent). However, this detail is not too important. It does not matter unless you have a zero-width float, since any other float or line will not fit into that zero-pixel region, move on down past it, and then adjust the high-water mark as appropriate.

Negative margins on floats

Every time I've talked about the top, right, bottom, and left edges of floats I've meant the margin edges. All of the same rules apply when the float in question has negative margins. A float with negative margins may have its top outer edge below its bottom outer edge, which is certainly a weird situation. Nonetheless, its top outer edge will be placed at the high-water mark or below it, even though the float may well be drawn above that high-water mark if it has a negative top margin. If the top margin is negative and large enough, the bottom-right step of the float might not actually become a new step because it would be above the high-water mark. It happens!

Another consequence of these rules for negative margins is that while floats with zero margins stack next to each other horizontally, floats with sufficiently large negative margins will not. To see that, consider the following two series of images. The first shows the positioning process for three floats with zero margins. The second shows the positioning process for the same three floats, with a large negative top margin.

7.svg

Floats stack next to each other because each float adds a new step, forcing future floats to move further right. When the floats have a large enough negative top margin, those steps are above the high-water mark, so the steps get removed. Thus, the exclusion zone is the same for every float, and the floats therefore overlap.

Positioning lines of text around floats

Text wraps around floats. What does that mean? Let's back up and first discuss how lines of text are positioned in the absence of floats.

A line of text is bound by a line box. Every line box is the child of a block box, and a line box's siblings are always line boxes. A line box is placed vertically right after the previous line box in its block, while the first line box in a block is placed at the “top content edge” of its block.

Once the vertical position and height of a line box is known, we can compute its left and right edges (and therefore dimensions). We do so by finding all floats horizontally adjacent to the box, then putting the left edge of the line at the furthest-right edge of a left float, and the right edge at the furthest-left edge of a right float.

Note that laying out line boxes does not consult the high-water mark or the exclusion zone. You can have situations like this:

12.svg

However, placing a line boxes changes where the next float would go if it were in normal flow, so placing line boxes may raise the high-water mark for the next float.

Conclusion

These rules are the nittiest-grittiest of details, but they also allow you to make accurate predictions of where floats will go and to debug float problems with more elegant means than guessing and checking. I hope the mental model I describe is useful to you!

Footnotes:

1

For example the two-column layout on my homepage uses floats.

2

Floats were introduced in version 2, and have stayed largely identical. I am working off of the CSS 2.1 rules, which is what Cassius implements, but have glanced over the rules in CSS 2.2 and in CSS 3 and found them similar.

3

You can also use CSS to style XML, SVG, and god knows what else, but let's stick to the simple cases.

4

Of a few different sorts: text, images, backgrounds, borders, and so on.

5

Its lesser-known cousin visibility: hidden generates a box but does not draw it.

6

Generated by text nodes in the HTML, not by elements.

7

Not generated by anything in the element tree.

8

In that case, the <div> will become a sibling of two separate <b> boxes—the part of the <b> before the <div>, and the part after—because a block box cannot be contained in an inline box. Here, a parent-child relationship between two elements becomes a sibling relationship between three boxes.

9

Because, say, they float so are roots of their own flow.

10

CSS allows both of these cases, with percentage heights and shrink-to-fit widths. Predictably, both concepts create referential loops. For example, a box might have a percentage height, so it is dependent on its parent's height, but that parent needs the child's height to compute its own height. In these cases, CSS specifies some “fallthrough” value, such as a height of 0 for the child.

11

Ignoring the infinite complexities of images, video, animations, OS components like buttons, and so on.

12

A right float would be placed at the right edge. In general, left and right floats behave totally symmetrically, so I will only discuss left floats here.