Srikanth Bandaru

Width flows down, height flows up

Why a child set to height: 50% can end up 1 line tall, and why a card won’t shrink past a long URL.

Contents4 sections
  1. Width goes in, height comes out
  2. Bug 1: a box asks for a size that doesn’t exist yet
  3. Bug 2: the content pushes a width back up
  4. 4 ways to fix a size bug

The browser works out widths from the top of the page down and heights from the bottom up. A parent tells each child how wide it may be, and the child answers with how tall it turned out. Most confusing CSS sizing bugs break that order. A box asks for a size that isn’t worked out yet, or its content pushes a width back up.

These 2 boxes are both 320px tall, and each holds a child set to height: 50%. Guess how tall each child is.

Two parents, one child each

Live: your browser’s own layout

Guess each child’s height. Your browser draws the answer as soon as you pick.

Box A

height: 50%

min-height: 320px

Box B

height: 50%

flex item in a height: 320px row

Both boxes are real CSS in your browser. A’s parent has only a min-height. B’s parent is a stretched flex item with no height of its own.

The usual rule says a percentage height needs a parent with a height set. Both parents look as if they have one, but only one child gets its 50%. Box A comes from Josh Comeau’s The Height Enigma. The first bug below explains both boxes.

Width goes in, height comes out

Matt Brubeck put the rule in one line while building a toy browser engine. “A block’s width depends on its parent, while its height depends on its children.”

It can’t work the other way round. A paragraph’s height is the number of lines it wraps into, and that depends on its width. Many widths give the same height, so a height can’t tell the browser which width to use.

Change one width, watch the heights

Live: your browser’s own layout

Drag the page width, or tap Make it narrower. Widths change from the top down, heights from the bottom up.

page: the width you set

nav

This paragraph wraps to fit whatever width the card is given, and the card grows taller to hold every line it needs.

A real flex row in your browser. Widths are read back from the page after every change.

The line under the numbers shows that whole range of widths for the height you’re looking at.

Bug 1: a box asks for a size that doesn’t exist yet

A percentage size is a fraction of the containing block, the box it measures against (usually its parent). A percentage width nearly always works, because widths arrive from above before the child is laid out. A percentage height asks for something the parent only knows after its children are done.

The spec’s word for a size the browser can work out without laying anything out is “definite”. Box A’s parent is 320px tall, but its height is still auto. The min-height only sets a floor under it. So the child’s 50% has nothing to read, and CSS 2.1 says it computes to auto. That’s 1 line.

Box B’s parent is a flex item (a direct child of a display: flex box) in a 320px row. It has no height of its own, but the default align-items: stretch makes it as tall as the row. The flexbox spec counts that stretched height as definite when the row’s height is. So the child reads 320px and takes half. The spec says it made this exception so percentages in flex items resolve “where authors expected them to resolve.”

Every fix for this bug makes a size definite before the child asks for it.

Bug 2: the content pushes a width back up

Widths don’t only flow down. Before the real layout, the browser works out each box’s min-content width from the bottom up. That’s the narrowest a box can be without overflowing, usually the width of its longest word or URL.

A flex item defaults to min-width: auto, which in practice means its min-content width. So a long URL in a card in a row sets a floor, and the row can’t shrink the card below it. Josh Comeau explains this case in his interactive guide to flexbox.

The floor repeats at every flex item on the way up. So min-width: 0 has to go on the flex item that holds the floor, which isn’t always the box you’re looking at.

Find the box holding the width

Live: your browser’s own layout

The card is wider than the page you set. Try each button and find the box holding the width.

page: the width you set

nav

Flexbox spec: automatic minimum size

drafts.csswg.org/css-flexbox-1/#min-size-auto

A real flex row in your browser: the page’s padding, a nav column, an 8px gap, then the card. The link clips, so its own minimum is already 0.

Letting the link shrink does nothing. It already clips, so its own minimum was 0. The card is the flex item holding the width. min-width: 0 or overflow: hidden on the card removes the floor, and the ellipsis appears.

A 1fr grid column (1 share of the free space) has the same floor. A long word can push the column wider than its container. That’s why Chris Coyier’s fix for that is minmax(0, 1fr).

4 ways to fix a size bug

Move Examples
Make it definite from above html, body { height: 100% }, an explicit height on the parent
Let the parent push it down align-items: stretch without height: 100%, flex: 1, grid cells
Skip the tree 100vh (the height of the visible browser window), 100svh (the same, with the mobile browser bars showing), position: absolute
Cut the upward flow min-width: 0 or overflow: hidden on the item holding the floor, minmax(0, 1fr), overflow-wrap: anywhere

When a box won’t fill or won’t shrink, find where its size comes from before you change any CSS. If it’s waiting for a height, use one of the first 3 rows. If its content set a minimum width, remove that minimum on the flex item that holds it.