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
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 layoutGuess each child’s height. Your browser draws the answer as soon as you pick.
Box A
How tall is the child?
min-height: 320px
Box B
How tall is the child?
flex item in a height: 320px row
Show the CSS
/* Box A */
.parent { min-height: 320px; }
.child { height: 50%; }
/* Box B */
.row { display: flex; height: 320px; }
.parent { /* no height */ }
.child { height: 50%; }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 layoutDrag the page width, or tap Make it narrower. Widths change from the top down, heights from the bottom up.
page: the width you set
This paragraph wraps to fit whatever width the card is given, and the card grows taller to hold every line it needs.
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 layoutThe card is wider than the page you set. Try each button and find the box holding the width.
page: the width you set
Flexbox spec: automatic minimum size
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.