04 · Flexbox in Depth: How Sizes Are Resolved¶
Lesson 03 was about positioning items. This lesson is about sizing them — the part
of Flexbox people learn by trial and error. The algorithm is short and entirely
predictable. Once you've done the arithmetic a few times, you'll know exactly what
flex: 1 does, why one column refuses to shrink, and why a code block blows your layout
out of its container.
The three properties¶
Each flex item has:
flex-basis— its starting size along the main axis, before free space is shared out. Defaultauto(use the item'swidth/height, or its content size if that'sautotoo).flex-grow— how many shares of positive free space it takes. Default0(don't grow).flex-shrink— how readily it gives up space when there's negative free space (overflow). Default1.
Write them with the flex shorthand:
| Shorthand | Means | Use for |
|---|---|---|
flex: initial (the default) |
0 1 auto |
Content-sized, can shrink, won't grow |
flex: auto |
1 1 auto |
Start at content size, share extra space |
flex: 1 |
1 1 0% |
Ignore content size, share all space equally |
flex: none |
0 0 auto |
Rigid: content size, never grows or shrinks |
flex: 0 0 250px |
fixed 250px | Sidebars, fixed columns |
flex: 2 1 200px |
start at 200px, grow with weight 2 |
Notice that flex: 1 sets the basis to 0, not auto. That single detail explains most
confusion about it.
Growing: sharing positive free space¶
The algorithm: free space = container size − sum of bases (minus gaps). Each growing
item gets free space × (its grow ÷ total grow) added to its basis.
We put two items in a 600px container, flex: 1 1 100px and flex: 2 1 200px, and
measured them in Chromium:
The arithmetic: bases sum to 300, so free space is 300. Grow factors total 3; item one gets 1/3 × 300 = 100 → 200px; item two gets 2/3 × 300 = 200 → 400px. Note that item two isn't "twice as wide" — it gets twice the extra space.
flex: 1 vs flex: auto¶
Three items with different amounts of text, in a 600px container:
With flex: 1, every basis is 0, so all 600px is free space, split equally — the items
end up equal regardless of content. With flex: auto, each starts at its content width
and only the leftover space is shared equally, so items with more text stay wider.
Choose flex: 1 for equal columns, flex: auto for "content-sized, but fill the row".
Shrinking: sharing negative free space¶
When the bases add up to more than the container, items shrink. Shrinking is weighted
by basis: an item's share of the overflow is proportional to flex-shrink × flex-basis.
Otherwise a small item could be shrunk to nothing while a big one barely changes.
Three items flex: 0 1 400px, 0 1 200px, 0 1 200px in 600px:
Overflow is 800 − 600 = 200. Scaled shrink factors are 400, 200, 200 (total 800). The first item loses 400/800 × 200 = 100 → 300px; each of the others loses 50 → 150px. Everything shrank by the same proportion (25%).
The min-width: auto trap¶
Here's the most common Flexbox bug. A 300px container with a fixed 100px sidebar and a
flex: 1 main column containing a long line of code in a <pre>:
<div class="layout">
<div class="side"></div>
<div class="main">
<pre>const url = "https://example.com/a/very/long/path/that/does/not/wrap";</pre>
</div>
</div>
You'd expect .main to be 200px. We measured it:
The main column grew to 546px, overflowing the 300px container. The reason: flex
items have min-width: auto by default, which for flex items means "don't shrink below
my content's minimum size." The content's minimum size is the unbreakable
<pre> line, so the item refuses to be narrower than that line, whatever flex-basis
says.
The fix is to allow the item to be smaller than its content:
.main {
flex: 1;
min-width: 0; /* allow shrinking below content size */
}
.main pre { overflow-x: auto; } /* and let the long content scroll */
The same thing happens with long URLs, wide tables, images with large intrinsic sizes and
nested flex rows. In a column layout it's min-height: auto that bites. Whenever a flex
child overflows mysteriously, try min-width: 0 (or overflow: hidden, which also
removes the automatic minimum) on it first.
Worked example: a layout that behaves at every width¶
A sidebar layout that puts the sidebar beside the content when there's room and above it when there isn't — with no media query:
<div class="with-sidebar">
<aside class="sidebar">…filters…</aside>
<main class="content">…results…</main>
</div>
.with-sidebar {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.sidebar {
flex: 1 1 15rem; /* ideally 15rem wide, may grow */
}
.content {
flex: 999 1 0; /* grows far faster than the sidebar */
min-width: 60%; /* but when it can't be at least 60% wide, wrap */
}
How it works: .content has a basis of 0 and a huge grow factor, so it takes nearly all
the free space and the sidebar stays close to 15rem. When the container gets narrow
enough that .content can't meet its min-width: 60% on the same line as the sidebar,
flex-wrap puts it on its own line, where both items grow to full width. We measured it
at three container widths:
1000px: .sidebar w=241 top=0 .content w=735 top=0
700px: .sidebar w=240 top=0 .content w=436 top=0
400px: .sidebar w=400 top=0 .content w=400 top=42
At 400px, a sidebar of 240px plus the 24px gap would leave .content less than its
240px minimum (60% of 400), so it wrapped below and both went full width. This "sidebar"
pattern (popularised by Heydon Pickering and Andy Bell's Every Layout) responds to the
container's width, not the viewport's — useful when the same component appears in a
wide main area and a narrow column.
How It Actually Works¶
The spec's "resolve flexible lengths" step is iterative:
- Compute each item's hypothetical main size: its flex-basis, clamped by its
min/max sizes (with
min-width: autoresolved to the content-based minimum). - Sum them (plus gaps). If the sum is less than the container, use grow factors; if more, use shrink factors.
- Distribute the free space in proportion to the factors (for shrink,
flex-shrink × basis). - Clamp: if any item's new size violates its
min-widthormax-width, freeze it at that limit. - Recalculate free space with the frozen items removed and repeat from step 3 for the rest, until nothing changes.
That loop explains the min-width: auto result: in the first pass the main item was
offered a width near 200px, violated its content-based minimum, got frozen at 546px, and
the layout simply overflowed.
It also explains why max-width on a growing item doesn't "waste" space — the item is
frozen at its max and the space is redistributed to its siblings in the next pass.
A final subtlety: flex-basis: auto means "look at width" (or height in a column),
and only if that is also auto does it use the content size. So width: 200px;
flex-basis: auto gives a 200px basis, while flex-basis: 0 ignores width entirely for
the basis — but width's sibling min-width still applies as a clamp.
Common mistakes¶
- Thinking
flex: 1means "grow" and being surprised that content size is ignored. - Expecting
flex-grow: 2to make an item twice as wide. It gets twice the extra space. - Overflowing flex items from long content — add
min-width: 0. widthon flex items expecting it to be final. It's only the basis (whenflex-basisisauto), and items can still grow or shrink from it.- Fixed sidebars without
flex-shrink: 0, which shrink on small screens.
Exercise¶
- Predict, then measure in devtools: three items with
flex: 1 1 100px,flex: 1 1 200pxandflex: 2 1 0in a 700px container. - Predict the widths of
flex: 0 1 300pxandflex: 0 3 300pxin a 400px container. (Hint: scaled shrink factors are 300 and 900.) - Recreate the
min-width: autotrap with a long URL instead of a<pre>, then fix it. - Build the worked-example sidebar layout. Resize the browser and find the width at which it wraps. Put the same component inside a 400px-wide box and confirm it wraps there too.