skip to content

In CSS, why does a container whose children are all floated collapse to zero height, and what makes it contain them again?

level: middleimportance: must knowfreq 62%

answer

  1. auto height counts in-flow children
  2. floats are out of flow
  3. container must contain its floats
  4. flow-root, or overflow as a side effect
  5. clearfix adds a cleared in-flow child

basics

~20 s

A block container with auto height measures only its in-flow children, and floats are out of flow, so it collapses. Make it contain floats by giving it display: flow-root, or add a cleared element after the floats.

solid answer

~50 s

A block container with `height: auto` computes its height from its in-flow children, and floated children are out of flow, so a row whose children are all floated has nothing to measure and collapses. The fix is to make the container contain its floats. The modern one-liner is `display: flow-root`: it establishes a block formatting context, so the container's auto height stretches to include the floats, with no clipping and no scrollbars. `overflow: hidden` or `overflow: auto` has the same containing side effect, but it also clips or scrolls anything that escapes the box, which quietly breaks dropdowns and sticky children. The old clearfix — an `::after` pseudo-element with `content: ""`, `display: block`, `clear: both` — works differently: it inserts an in-flow child that is pushed below the floats, so the parent's height reaches down to it.

code

css · 14 lines
css
.row-legacy::after {
  content: "";
  display: block;
  clear: both;
}

.row-modern {
  display: flow-root;
}

.col {
  float: left;
  width: 33.333%;
}

go deeper

for a junior

Be able to recognise the symptom — a wrapper that renders as a thin line above its floated children — and name one fix out loud, ideally display: flow-root.

for a middle

Explain the mechanic: auto height measures in-flow children only, floats are out of flow, and a block formatting context root is the exception that measures the floats it contains.

for a senior

Show judgment between the fixes: why overflow: hidden is a trap in a component that has dropdowns or shadows, and why converting the row to flex or grid usually beats containing the floats at all.

for a principal

Own the codebase-wide call — whether to keep a clearfix utility alive for compatibility or delete it in a sweep, and how you stop layout hacks from being copied into new components.

## The symptom You write a row of floated columns, put a border on the wrapper, and the border draws as a thin line across the top while the columns hang out below it: ```css .row { border: 1px solid #333; } .col { float: left; width: 33.333%; } ``` Nothing is broken. This is the specified behaviour of auto height plus floats, and it is the single most-asked float question because every legacy stylesheet carries a workaround for it. ## Why the height ignores floats A float is *out of flow*. When a block-level box with `height: auto` sizes itself, it measures the distance from its top content edge to the bottom edge of its last **in-flow** child (or its last line box). Out-of-flow boxes — floats and absolutely positioned boxes — are simply not in that measurement. If every child is floated, the container has zero in-flow content, so its content height is zero. There is one exception, and it is the whole answer to the question: a box that establishes a **block formatting context** computes its auto height so that it also includes the bottom edge of the lowest float it contains. "Contains" is the operative word — floats are laid out inside the nearest formatting-context root, so a container that is a root both owns its floats and measures them. ## Fix 1: display: flow-root `display: flow-root` exists for exactly this job. The outer display stays block, so the element still lays out as a block in its parent; the inner display becomes an independent flow root, so it contains its floats and its auto height grows to fit them. ```css .row { display: flow-root; } ``` No pseudo-element, no clipping, no scrollbars, no extra markup. It also stops margins from collapsing through the box, which is a bonus rather than the point here. It is supported in every current browser — Chrome and Firefox from 2017, Safari from version 13 in 2019 — so on any modern baseline it is the default answer. ## Fix 2: overflow (the accidental one) `overflow: hidden`, `auto`, or `scroll` on the container also makes it a formatting-context root, so it contains floats as a **side effect**. This was the popular one-line fix for years, and it is still what you will find in old code. Its cost is that overflow is a real, load-bearing property: `hidden` clips any child that deliberately escapes the box (tooltips, drop shadows, absolutely positioned menus), and `auto` can produce a scrollbar you never asked for. It can also defeat a descendant that relies on scrolling from an ancestor. Prefer `flow-root` unless you actually wanted the clipping. ## Fix 3: the clearfix The historical workaround does not create a formatting context at all. It manufactures an in-flow child that sits *below* the floats: ```css .row::after { content: ""; display: block; clear: both; } ``` The generated box is in flow, `clear: both` pushes its top edge below the bottom margin edge of the preceding floats, and because it is the last in-flow child, the parent's auto height now reaches it. Older variants used `display: table` on `::before` and `::after` to also stop margins collapsing through; even older code used a literal empty `<div>` with `clear: both`, which is presentational markup and the reason the pseudo-element version won. A clearfix still works, and you should be able to read one, but it is boilerplate that `flow-root` replaced with a single declaration. ## Fix 4: change the layout model `float` does not apply to flex or grid items — it computes to have no effect on them. So turning the container into `display: flex` or `display: grid` makes the whole problem evaporate, because the children stop being floats and become items that the container sizes itself around. On a modern codebase this is usually the real fix: the floats were only ever there to put boxes side by side. ## What does *not* fix it - Putting `clear: both` on the floats themselves. Clearance moves a box down relative to earlier floats; it does not put it back in flow, so the parent still cannot measure it. - `position: relative` on the container. That creates a containing block for positioned descendants and has nothing to do with float containment. - Setting a fixed `height` on the container. That hides the symptom at one viewport width and breaks at every other one. ## What to say in an interview Name the rule (auto height counts in-flow children only), name the exception (a formatting-context root contains and measures its floats), then rank the fixes: `flow-root` first, `overflow` as the legacy accident with clipping costs, clearfix as the pseudo-element that adds a cleared in-flow child, and "stop using floats for layout" as the real answer in new code.

  • Why did overflow: hidden become the popular one-line fix, and what does it cost you?
    It makes the container a block formatting context root, and containing floats falls out of that for free — one declaration, no pseudo-element. The cost is that overflow is not a no-op: `hidden` clips anything meant to escape the box, such as tooltips, dropdowns or shadows, and `auto` can add an unwanted scrollbar. `display: flow-root` gives you the containment without touching overflow.
  • If I put clear: both on the last floated child, does the parent get its height back?
    No. Clearance only moves that box down past earlier floats; it is still a float and still out of flow, so the parent's auto height ignores it exactly as before. The cleared element in a clearfix works because it is a normal in-flow block, not a float.
  • What happens to float: left on a child if I switch the container to display: flex?
    Nothing — `float` does not apply to flex items, so the declaration is ignored and the child is laid out as a flex item. That is why converting a float row to flex or grid removes the collapsed-height problem entirely rather than needing any containment trick.

Auto height is a ruler that only measures things standing on the floor; floats are hanging from the ceiling, so the ruler reads zero until you tell the room to include them.

saying these in an interview costs you the question

  • Says floats are absolutely positioned, so ancestors never see them
  • Claims content: "" is what creates the formatting context
  • Thinks clear: both on the floats themselves restores parent height
  • Reaches for a fixed height on the wrapper as the fix
  • Cannot name anything newer than the clearfix hack

context