HTML & CSS NotesRohit’s interview study guide
Chapter 06

Flexbox

One-dimensional layout: how a flex container lays its items along a main axis, how justify-content and the align-* properties share out space, the exact arithmetic of flex-grow, flex-shrink and flex-basis, what flex: 1 really means, wrapping, the min-width: auto overflow trap, and the layouts you'll build with it every week.

8 topics
Parts marked Advanced are extra depth. Skip them on a first read or a quick revision.

#The flex model: main axis and cross axis

Added

display: flex turns an element into a flex container, and its direct children (only those, not grandchildren) become flex items. Flexbox lays the items out in one direction at a time: along a line, the main axis, set by flex-direction. The other direction, at right angles to it, is the cross axis. With the default flex-direction: row the main axis runs in the inline direction (left to right in English) and the cross axis top to bottom; with column they swap.

Every flex property is defined in terms of those two axes, not "horizontal" and "vertical". justify-* works along the main axis; align-* works across it. That's why justify-content: center centres horizontally in a row but vertically in a column. The mental model: the items are beads on a string. justify-content slides the beads along the string; align-items moves each bead up or down off the string; flex-direction turns the whole string around.

Two defaults explain most first impressions of flexbox: items sit in a single line and don't wrap (flex-wrap: nowrap), and items stretch across the cross axis to fill the line (align-items: normal, which behaves as stretch for flex items). That's why a row of flex items becomes equal height for free.

The same items in a row and in a column
CSS
.row, .col { display: flex; width: 300px; background: #f1f5f9; margin-bottom: 10px; }
.row { height: 80px; }
.col { flex-direction: column; height: 140px; }
.item { background: #93c5fd; box-shadow: inset 0 0 0 1px #f1f5f9; }
.row .item { width: 60px; }   /* main size in a row */
.col .item { height: 40px; }  /* main size in a column */
HTML
<div class="row"><div class="item">1</div><div class="item">2</div><div class="item">3</div></div>
<div class="col"><div class="item">1</div><div class="item">2</div><div class="item">3</div></div>
Resultlive, in your browser
What’s happening
  1. In .row the main axis is horizontal. Each item is 60px wide (its main size), and the items are placed one after another from the main-start edge: 0, 60 and 120px. Unlike blocks, they don't stack.
  2. Nothing sets their height, so the default alignment stretches them across the cross axis: each is 80px, the full height of the container.
  3. In .col (flex-direction: column) the main axis is vertical. Each item's main size is now its 40px height, and they are placed at 0, 40 and 80px down.
  4. The cross axis is now horizontal, so they stretch to the container's full 300px width, like blocks would.
  5. Same items, same properties; only the axes turned. The computed align-items is normal, which for flex items means stretch.
AdvancedBlockified items and uncollapsed margins

Becoming a flex item changes a box in two ways people trip over. Its display is blockified (an inline span becomes block, so width and height apply), and margins between flex items never collapse, unlike margins between blocks (see Margin collapsing).

Margins in a flex column vs a block
CSS
.flexcol { display: flex; flex-direction: column; background: #f1f5f9; margin-bottom: 10px; }
.blockcol { background: #f1f5f9; }
.flexcol span, .blockcol div { margin: 10px 0; height: 20px; width: 120px; background: #fde68a; }
HTML
<div class="flexcol"><span id="f1">span</span><span id="f2">span</span></div>
<div class="blockcol"><div id="b1">div</div><div id="b2">div</div></div>
Resultlive, in your browser
What’s happening
  1. The spans are flex items, so their computed display is block and width: 120px takes effect.
  2. Between the two flex items there are 20px: the first one's 10px bottom margin plus the second one's 10px top margin. Flex items' margins add up.
  3. Between the two block divs there are only 10px: adjacent vertical margins in block layout collapse into the larger one.
  4. The first flex item's top margin stays inside its container (10px from its top). The first block's top margin collapses through its parent (which has no padding or border) and pushes the parent down instead, so the div sits flush with the parent's top. A flex container establishes a new formatting context, which never lets margins collapse through it.
AdvancedDirection follows the writing direction

row means "the inline direction", so in a right-to-left document a row starts at the right. row-reverse and column-reverse swap main-start and main-end. Both are visual only: the DOM order, and so the reading and tab order, don't change.

row in RTL, and row-reverse
CSS
.r { display: flex; width: 300px; height: 30px; background: #f1f5f9; margin-bottom: 8px; }
.r i { width: 60px; flex: none; background: #c4b5fd; font-style: normal; text-align: center; box-shadow: inset 0 0 0 1px #f1f5f9; }
.rev { flex-direction: row-reverse; }
HTML
<div class="r" id="ltr"><i>1</i><i>2</i><i>3</i></div>
<div class="r" id="rtl" dir="rtl"><i>1</i><i>2</i><i>3</i></div>
<div class="r rev" id="rev"><i>1</i><i>2</i><i>3</i></div>
Resultlive, in your browser
What’s happening
  1. In #ltr the row starts at the left: item 1 at 0px, 2 at 60px, 3 at 120px.
  2. In #rtl (dir="rtl") the inline direction runs right to left, so main-start is the right edge: item 1 is at 240px (its right edge touches the container's right edge), then 180, then 120.
  3. row-reverse in a left-to-right page gives the same picture by swapping main-start and main-end. Note that the items also pack towards the right now: justify-content: flex-start means "towards main-start", which is the right in this case.
  4. Use dir/writing modes for languages, and avoid *-reverse for content whose order matters (see the order gotcha in Flex container properties).
Where does the item go?
CSS
.box { display: flex; flex-direction: column; justify-content: center; align-items: flex-end;
       width: 300px; height: 200px; }
.item { width: 60px; height: 40px; }
Show answer

Vertically centred and against the right edge: its top-left corner is at (240, 80). In a column, justify-content works vertically and align-items horizontally.

CSS
.box { display: flex; flex-direction: column; justify-content: center; align-items: flex-end; width: 300px; height: 200px; background: #f1f5f9; }
.item { width: 60px; height: 40px; background: #93c5fd; }
HTML
<div class="box"><div class="item"></div></div>
Resultlive, in your browser
What’s happening
  1. flex-direction: column makes the main axis vertical.
  2. justify-content: center shares the free main-axis space equally: 200 − 40 = 160px, 80px above and 80px below, so the top is at 80px.
  3. align-items: flex-end aligns on the cross axis, now horizontal, to the cross-end edge: the right. 300 − 60 = 240px from the left.
  4. If you swapped the direction back to row, the same two declarations would put the item at (120, 160): centred horizontally, at the bottom.

#Flex container properties

Added

Flexbox splits its properties between the container, which sets the direction, wrapping, alignment and gaps for all items, and the items, which say how each one grows, shrinks and aligns itself. Knowing which side a property belongs to answers half of all "why doesn't this work" questions: justify-content on an item, or flex: 1 on a container, does nothing.

On the containerWhat it doesInitial value
display: flex / inline-flexmakes a block-level / inline-level flex container—
flex-directionmain axis: row, row-reverse, column, column-reverserow
flex-wrapsingle line (nowrap) or multiple lines (wrap, wrap-reverse)nowrap
flex-flowshorthand for flex-direction + flex-wraprow nowrap
justify-contentdistributes free space along the main axisnormal (acts as flex-start)
align-itemsdefault cross-axis alignment of items in their linenormal (acts as stretch)
align-contentdistributes lines on the cross axis (wrapping containers)normal (acts as stretch)
gap, row-gap, column-gapfixed space between items and between linesnormal (0 in flex)
On the itemWhat it doesInitial value
flex-growshare of positive free space0
flex-shrinkshare of negative free space (weighted by basis)1
flex-basisstarting main size before growing/shrinkingauto
flexshorthand for the three above0 1 auto
align-selfoverrides align-items for one itemauto
ordervisual position among siblings0
margin: autoabsorbs free space on that side—

The mental model: the container is a shelf, the items are books. The shelf decides which way the books face, whether they spill onto a second shelf, and how leftover space is spread; each book decides only how much it's willing to stretch or squash, and whether it sits differently from the rest.

flex vs inline-flex
CSS
.wrap { width: 400px; background: #f8fafc; }
.flex, .iflex { background: #f1f5f9; outline: 1px dashed #94a3b8; }
.flex { display: flex; margin-bottom: 10px; }
.iflex { display: inline-flex; vertical-align: top; }
i { width: 50px; height: 30px; background: #93c5fd; box-shadow: inset 0 0 0 1px #f1f5f9; }
HTML
<div class="wrap">
  <div class="flex"><i></i><i></i></div>
  <div class="iflex" id="i1"><i></i><i></i></div>
  <div class="iflex" id="i2"><i></i><i></i></div>
</div>
Resultlive, in your browser
What’s happening
  1. display: flex makes a block-level container: from the outside it behaves like a div, filling the 400px wrapper and starting on its own line.
  2. display: inline-flex makes an inline-level container: from the outside it behaves like an inline-block, as wide as its content (two 50px items, 100px) and sitting on a line with other inline content. The two inline-flex boxes share a line, side by side.
  3. On the inside both lay their items out identically. The difference is only the outer display type; display: flex is shorthand for display: block flex in the two-value syntax, and inline-flex for display: inline flex.
  4. Use inline-flex for small components that live inside text or toolbars: a button with an icon and a label, a badge.
Advancedorder: visual order only

order moves an item along the main axis without touching the DOM: items are laid out by ascending order (default 0), ties in source order.

order changes where an item is drawn, not where it is in the DOM
CSS
.track { display: flex; gap: 6px; width: 300px; background: #f1f5f9; }
.track a { width: 80px; height: 30px; display: grid; place-items: center; background: #fde68a; color: #1f2430; }
#i1 { order: 1; }
HTML
<nav class="track"><a href="#" id="i1">First</a><a href="#" id="i2">Second</a><a href="#" id="i3">Third</a></nav>
Resultlive, in your browser
What’s happening
  1. All three links have order: 0 except #i1, which has order: 1. Items with lower order come first, so #i2 and #i3 are drawn first and #i1 goes to the end, 172px in (two 80px items and two 6px gaps).
  2. The DOM order is still i1, i2, i3. Screen readers read in DOM order, and the Tab key moves focus in DOM order, so a keyboard user's focus jumps from the last visible link to the first and back.
  3. That disconnect is why order, row-reverse and column-reverse are risky for anything interactive or meaningful. WCAG's "meaningful sequence" and "focus order" criteria are about exactly this. Use them for purely visual rearrangements, and change the source order when the order matters.
Container or item?

Which of these do nothing where they're written? .item { justify-content: center }, .list { flex: 1 } (on a flex container whose parent is a block), .item { align-self: center }, .list { align-items: center }.

Show answer

The first two. justify-content is a container property, so on an item it only affects that item's own children (if it's a flex container itself). flex is an item property, so it does nothing on an element whose parent isn't a flex container. align-self belongs on items and align-items on containers, so those two work.

What’s happening
  1. Ask of every property: does it describe how my children are laid out (container), or how I behave inside my parent's flex layout (item)?
  2. justify-content, align-items, align-content, flex-direction, flex-wrap, gap are about children: container.
  3. flex, flex-grow, flex-shrink, flex-basis, align-self, order are about the element itself: item, and only meaningful if the parent is a flex container.
  4. An element can be both at once (a flex item that is also a flex container), which is how nested flex layouts work.

#justify-content, align-items and align-content

Added

Alignment is about free space: whatever is left of the container after the items have their sizes. justify-content decides how the free space on the main axis is shared out between and around the items. align-items (and align-self per item) positions each item within its line on the cross axis. align-content only matters when there are several lines (a wrapping container): it shares the free cross-axis space between the lines.

The mental model: justify-content is spacing books along a shelf; align-items is how each book sits on its shelf (on the bottom, in the middle, stretched to the shelf above); align-content is how the shelves are spaced in the bookcase. One container can have all three.

For justify-content the arithmetic is simple. With free space F and n items: space-between puts F / (n − 1) between items and nothing at the ends; space-around gives each item F / n split half on each side, so the ends get half a gap; space-evenly makes n + 1 equal gaps of F / (n + 1), ends included.

justify-content: three 60px items in 300px
CSS
.track { display: flex; width: 300px; height: 30px; background: #f1f5f9; margin: 2px 0 10px; }
.track i { flex: none; width: 60px; background: #93c5fd; box-shadow: inset 0 0 0 1px #f1f5f9; }
code { font-size: 12px; }
HTML
<code>flex-start</code><div class="track" style="justify-content: flex-start"><i></i><i></i><i></i></div>
<code>center</code><div class="track" style="justify-content: center"><i></i><i></i><i></i></div>
<code>flex-end</code><div class="track" style="justify-content: flex-end"><i></i><i></i><i></i></div>
<code>space-between</code><div class="track" style="justify-content: space-between"><i></i><i></i><i></i></div>
<code>space-around</code><div class="track" style="justify-content: space-around"><i></i><i></i><i></i></div>
<code>space-evenly</code><div class="track" style="justify-content: space-evenly"><i></i><i></i><i></i></div>
Resultlive, in your browser
What’s happening
  1. Three 60px items take 180px of the 300px track, so the free space F is 120px. Every value is a different way of placing those 120px.
  2. flex-start (the effective default) puts all of it at the end: 0, 60, 120. flex-end puts it all at the start: 120, 180, 240. center splits it 60/60: 60, 120, 180.
  3. space-between: 120 / (3 − 1) = 60px between items, none at the edges: 0, 120, 240.
  4. space-around: 120 / 3 = 40px per item, 20px on each side of it. The first item starts at 20px, and between two items there are 20 + 20 = 40px: 20, 120, 220.
  5. space-evenly: 120 / (3 + 1) = 30px for each of the four gaps: 30, 120, 210.
  6. The middle item is at 120px in every distributed case, since the layouts are symmetric. If the items had flex-grow, there would be no free space left and all six would look the same: justify-content only distributes what growing leaves over.
Advancedalign-items and align-self
align-items: stretch, flex-start, center, flex-end
CSS
.box { display: flex; gap: 10px; width: 300px; height: 100px; background: #f1f5f9; margin: 2px 0 10px; }
.box > div { width: 60px; background: #fde68a; }
.a { height: 20px; } .b { height: 40px; }
.c i { display: block; height: 20px; }   /* .c has no height: its content is 20px */
code { font-size: 12px; }
HTML
<code>stretch</code><div class="box" style="align-items: stretch"><div class="a"></div><div class="b"></div><div class="c"><i></i></div></div>
<code>flex-start</code><div class="box" style="align-items: flex-start"><div class="a"></div><div class="b"></div><div class="c"><i></i></div></div>
<code>center</code><div class="box" style="align-items: center"><div class="a"></div><div class="b"></div><div class="c"><i></i></div></div>
<code>flex-end</code><div class="box" style="align-items: flex-end"><div class="a"></div><div class="b"></div><div class="c"><i></i></div></div>
Resultlive, in your browser
What’s happening
  1. Each box is 100px tall. .a is 20px, .b is 40px, and .c has no height of its own (its content is 20px).
  2. stretch: .c stretches to the full 100px. .a and .b keep their heights: stretch only affects items whose cross size is auto. A fixed height (or max-height) wins.
  3. flex-start: everything at the top, and .c is just its 20px content.
  4. center: each item is centred in the 100px line: .a and .c at (100 − 20) / 2 = 40px, .b at (100 − 40) / 2 = 30px.
  5. flex-end: bottoms aligned: 80, 60 and 80px.
  6. align-self on a single item overrides the container's align-items for that item only, with the same values. (justify-self and justify-items are ignored in flexbox: there's one item per spot on the main axis, so use justify-content or auto margins.)
Advancedbaseline: lining up the text, not the boxes

align-items: baseline aligns the items' first text baselines, so labels of different font sizes or paddings sit on one line, the way they would in a sentence. The marker in each item below is a zero-height inline-block, whose bottom sits exactly on that item's baseline.

baseline vs flex-start
CSS
.box { display: flex; gap: 10px; background: #f1f5f9; margin-bottom: 10px; }
.box > div { background: #fde68a; }
.big { font-size: 30px; padding-top: 20px; }
.pad { padding-top: 40px; }
.mark { display: inline-block; width: 6px; height: 0; outline: 1px solid #dc2626; }
HTML
<div class="box" id="base" style="align-items: baseline">
  <div>Aa<i class="mark"></i></div><div class="big">Aa<i class="mark"></i></div><div class="pad">Aa<i class="mark"></i></div>
</div>
<div class="box" id="start" style="align-items: flex-start">
  <div>Aa<i class="mark"></i></div><div class="big">Aa<i class="mark"></i></div><div class="pad">Aa<i class="mark"></i></div>
</div>
Resultlive, in your browser
What’s happening
  1. The three items have different font sizes and top paddings, so with flex-start their tops line up but their text doesn't: the markers (baselines) are more than 10px apart.
  2. With align-items: baseline the browser moves each item on the cross axis so that its first baseline lands on a shared line. The markers' positions match to within half a pixel.
  3. This is what you want for a heading next to a smaller "View all" link, or form labels next to inputs of different heights. It's a font-metric feature, so the test checks equality, not pixel positions.
Advancedalign-content: spacing the lines

align-content distributes free cross-axis space between lines, so it needs a container that wraps onto more than one line. The demo has five 100 × 40px items in a 300 × 140px wrapping container: two lines of 40px, leaving 60px of free cross space.

align-content with two lines, and with one
CSS
.box { display: flex; flex-wrap: wrap; width: 300px; height: 140px; background: #f1f5f9; margin: 2px 0 10px; }
.box i { flex: none; width: 100px; height: 40px; background: #bbf7d0; box-shadow: inset 0 0 0 1px #f1f5f9; }
.single { flex-wrap: nowrap; }
code { font-size: 12px; }
HTML
<code>flex-start</code><div class="box" style="align-content: flex-start"><i></i><i></i><i></i><i></i><i></i></div>
<code>center</code><div class="box" style="align-content: center"><i></i><i></i><i></i><i></i><i></i></div>
<code>space-between</code><div class="box" style="align-content: space-between"><i></i><i></i><i></i><i></i><i></i></div>
<code>normal (stretch)</code><div class="box"><i></i><i></i><i></i><i></i><i></i></div>
<code>center, but nowrap (one line)</code><div class="box single" style="align-content: center"><i></i></div>
Resultlive, in your browser
What’s happening
  1. Three 100px items fit on a 300px line, so the five items make two lines: items 1–3 and items 4–5. The test measures the top of item 1 (line 1) and item 4 (line 2). Free cross space: 140 − 2 × 40 = 60px.
  2. flex-start packs the lines at the top: 0 and 40px. center puts 30px above and 30px below: 30 and 70px. space-between puts all 60px between the lines: 0 and 100px.
  3. The default, normal, behaves as stretch: each line grows by 60 / 2 = 30px to 70px. The items have a fixed 40px height, so they don't stretch with it and sit at the top of their (taller) line: 0 and 70px.
  4. In the last box, flex-wrap: nowrap makes a single-line container. Its one line always fills the container's cross size, so there's no space between lines to distribute: align-content: center has no effect, and the item stays at the top. Use align-items for single-line containers.
Where does each item start?

A 400px flex container has four 50px items and justify-content: space-around. Where does each item start?

Show answer

At 25, 125, 225 and 325px.

CSS
.track { display: flex; justify-content: space-around; width: 400px; height: 30px; background: #f1f5f9; }
.track i { flex: none; width: 50px; background: #93c5fd; }
HTML
<div class="track"><i></i><i></i><i></i><i></i></div>
Resultlive, in your browser
What’s happening
  1. The items take 4 × 50 = 200px, so the free space is 400 − 200 = 200px.
  2. space-around gives each item an equal share, 200 / 4 = 50px, split half before and half after it: 25px on each side.
  3. The first item starts at 25px. Between neighbours there are 25 + 25 = 50px, so each next item starts 50 + 50 = 100px later: 125, 225, 325.
  4. The last item ends at 375px, leaving 25px at the end: half-size gaps at the edges are what distinguishes space-around from space-evenly.

#flex-grow, flex-shrink and flex-basis

Added

These three item properties decide every flex item's main size, and the algorithm is mechanical enough to compute by hand, which is exactly what interviewers ask you to do.

  1. Each item starts from its flex basis: flex-basis if it's set to a length; if it's auto, the item's width (or height in a column); if that's auto too, its content size.
  2. Add up the bases, plus margins and gaps, and compare with the container: free space = container size − sum of bases − margins − gaps.
  3. If free space is positive, it's handed out in proportion to flex-grow: item gets free × grow / sum of grows. With flex-grow: 0 (the default) items don't grow.
  4. If free space is negative (the items overflow), the overflow is taken away in proportion to flex-shrink × flex basis, the scaled shrink factor: big items give up more than small ones even with equal flex-shrink. flex-shrink defaults to 1, so items shrink unless you say 0.
  5. If an item hits its min-width/max-width, it's frozen at that size and the remaining space is shared out again among the others.

The mental model: the basis is each item's opening bid; grow factors are shares of the surplus; shrink factors, weighted by size, are how much of the shortfall each one absorbs, so the biggest bidder pays most.

Growing: bases 100/150/50, grow 1/2/1, in 600px
CSS
.c { display: flex; width: 600px; height: 40px; background: #f1f5f9; }
.c > div { background: #93c5fd; box-shadow: inset 0 0 0 1px #f1f5f9; font-size: 12px; }
#a { flex-basis: 100px; flex-grow: 1; }
#b { flex-basis: 150px; flex-grow: 2; }
#c { flex-basis: 50px; flex-grow: 1; }
HTML
<div class="c"><div id="a">a: 100 + 1×</div><div id="b">b: 150 + 2×</div><div id="c">c: 50 + 1×</div></div>
Resultlive, in your browser
What’s happening
  1. Bases: 100 + 150 + 50 = 300px. Free space: 600 − 300 = 300px, positive, so items grow.
  2. Grow factors add up to 1 + 2 + 1 = 4, so one share is 300 / 4 = 75px.
  3. #a gets 1 share: 100 + 75 = 175px. #b gets 2 shares: 150 + 150 = 300px. #c gets 1 share: 50 + 75 = 125px. Measured: 175, 300, 125.
  4. Note that #b doesn't end up "twice as wide" as #a (300 vs 175): flex-grow divides the free space, not the total width. To get widths in a 1 : 2 : 1 ratio, start every basis at 0 (flex: 1, flex: 2; see the flex shorthand).
AdvancedShrinking is weighted by the basis
Shrinking: equal factors, and unequal ones
CSS
.c { display: flex; width: 300px; height: 40px; background: #f1f5f9; margin-bottom: 8px; }
.c > div { background: #fca5a5; box-shadow: inset 0 0 0 1px #f1f5f9; font-size: 12px; }
#a1 { flex: 0 1 300px; } #a2 { flex: 0 1 100px; }
#b1 { flex: 0 1 200px; } #b2 { flex: 0 3 200px; }
HTML
<div class="c"><div id="a1">300, shrink 1</div><div id="a2">100, 1</div></div>
<div class="c"><div id="b1">200, shrink 1</div><div id="b2">200, shrink 3</div></div>
Resultlive, in your browser
What’s happening
  1. Row 1: bases 300 + 100 = 400px in a 300px container, so the free space is −100px.
  2. Both have flex-shrink: 1, but the shrink is weighted by the basis: scaled factors are 1 × 300 = 300 and 1 × 100 = 100, total 400. The first loses 100 × 300/400 = 75px → 225px; the second loses 100 × 100/400 = 25px → 75px. Both shrank by the same 25%, which is the point of the weighting: proportions are kept.
  3. Row 2: bases 200 + 200 = 400, free space −100px again. Scaled factors 1 × 200 = 200 and 3 × 200 = 600, total 800. The first loses 100 × 200/800 = 25px → 175px; the second loses 100 × 600/800 = 75px → 125px.
  4. If shrinking weren't weighted by size, a 100px icon and a 300px text block would lose the same number of pixels, and the icon would vanish first. flex-shrink: 0 (or flex: none) opts an item out entirely, which is what you want for icons, avatars and buttons.
AdvancedGrow factors that sum below 1

If the flex-grow values add up to less than 1, items take only that fraction of the free space, and the rest stays empty.

flex-grow: 0.25 + 0.25
CSS
.c { display: flex; width: 400px; height: 40px; background: #f1f5f9; }
.c > div { flex: 0.25 1 100px; background: #bbf7d0; box-shadow: inset 0 0 0 1px #f1f5f9; }
HTML
<div class="c"><div id="x">0.25</div><div id="y">0.25</div></div>
Resultlive, in your browser
What’s happening
  1. Bases 100 + 100 = 200px, so 200px are free.
  2. The grow factors add up to 0.5, less than 1. The spec then hands out only 200 × 0.5 = 100px, split by the factors: 50px each. Measured: 150 and 150px.
  3. 100px stay unused at the end. With factors of 0.5 each (sum 1) they'd take all of it; any sum of 1 or more distributes everything. This is why flex-grow: 0.5 alone on a single item makes it take half the free space, not all of it.
AdvancedMin and max sizes freeze items
flex: 1 on three items, the middle one capped at 100px
CSS
.c { display: flex; width: 600px; height: 40px; background: #f1f5f9; }
.c > div { flex: 1 1 0; background: #c4b5fd; box-shadow: inset 0 0 0 1px #f1f5f9; }
#m { max-width: 100px; background: #fde68a; }
HTML
<div class="c"><div id="l">flex: 1</div><div id="m">max 100</div><div id="r">flex: 1</div></div>
Resultlive, in your browser
What’s happening
  1. All bases are 0, so all 600px are free; three equal grow factors would give 200px each.
  2. But #m has max-width: 100px. Its 200px violates that, so it's frozen at 100px and taken out of the distribution.
  3. The algorithm runs again for the unfrozen items: 600 − 100 = 500px left for two items with equal factors, 250px each. Measured: 250, 100, 250.
  4. The same freezing happens with min-width, and with the automatic minimum size of items that contain long content, which is the cause of the overflow bug in Why flex items overflow.
Advancedflex-basis vs width
Which size is the starting point?
CSS
.c { display: flex; width: 600px; height: 30px; background: #f1f5f9; margin-bottom: 6px; }
.c > div { flex-grow: 0; background: #93c5fd; }
#p1 { width: 300px; flex-basis: 100px; }
#p2 { width: 80px; }
#p3 { flex-basis: 100px; padding: 0 20px; }
#p4 { flex-basis: 100px; padding: 0 20px; box-sizing: border-box; }
#p5 { flex-basis: 100px; min-width: 150px; }
HTML
<div class="c"><div id="p1">basis 100, width 300</div></div>
<div class="c"><div id="p2">width 80, basis auto</div></div>
<div class="c"><div id="p3">basis 100 + padding</div></div>
<div class="c"><div id="p4">same, border-box</div></div>
<div class="c"><div id="p5">basis 100, min 150</div></div>
Resultlive, in your browser
What’s happening
  1. #p1 has both width: 300px and flex-basis: 100px; in a flex row the basis wins: 100px. width is only consulted when the basis is auto.
  2. #p2 has flex-basis: auto (the default), so its basis is its width, 80px.
  3. flex-basis follows box-sizing like width does. #p3 is content-box, so its content is 100px and the 2 × 20px padding come on top: 140px.
  4. #p4 is border-box, so the 100px include the padding: 100px.
  5. #p5 has a 100px basis but min-width: 150px, and min/max constraints always win over the flex result: 150px.
How wide is each item?
CSS
.c { display: flex; gap: 20px; width: 500px; }
.a { flex: 1 1 100px; }
.b { flex: 2 1 100px; }
Show answer

.a ≈ 193.33px and .b ≈ 286.67px.

CSS
.c { display: flex; gap: 20px; width: 500px; height: 30px; background: #f1f5f9; }
.a { flex: 1 1 100px; background: #93c5fd; }
.b { flex: 2 1 100px; background: #c4b5fd; }
HTML
<div class="c"><div class="a">a</div><div class="b">b</div></div>
Resultlive, in your browser
What’s happening
  1. Bases: 100 + 100 = 200px. Don't forget the gap: free space = 500 − 200 − 20 = 280px.
  2. Grow factors 1 and 2, total 3: one share is 280 / 3 ≈ 93.33px.
  3. .a = 100 + 93.33 ≈ 193.33px, .b = 100 + 186.67 ≈ 286.67px. Measured to within 0.1px.
  4. The common mistake is to forget the gap (giving 200 and 300) or to split the whole 500px 1 : 2 (giving 166.67 and 333.33), which would only be right with zero bases and no gap.

#The flex shorthand: what flex: 1 really means

Added

flex sets flex-grow, flex-shrink and flex-basis in one go, and its one-value forms don't mean what people guess. flex: 1 is 1 1 0%: grow, shrink, and start from a basis of zero. flex: auto is 1 1 auto: grow and shrink, but start from the content size. That single difference in basis decides whether items end up equal or proportional to their content.

You writegrowshrinkbasisMeaning
(nothing) / flex: initial01autocontent size, shrink if needed, never grow
flex: auto11autostart from content, share the leftover
flex: none00autorigid: exactly its content/width
flex: 1110%equal columns (all space shared out)
flex: 2210%twice the share of a flex: 1 sibling
flex: 0010%basis 0, no growing: shrinks to its minimum
flex: 100px11100pxa one-length value is the basis, and grow becomes 1
flex: 2 3230%two numbers are grow and shrink

The mental model: flex: 1 throws every item's opening bid away and splits the whole container by the factors; flex: auto keeps the bids and splits only the leftover.

flex: 1 vs flex: auto with unequal content
CSS
.c { display: flex; width: 600px; margin-bottom: 8px; background: #f1f5f9; }
.c > div { background: #93c5fd; box-shadow: inset 0 0 0 1px #f1f5f9; }
.c i { display: inline-block; vertical-align: top; height: 30px; background: #1e40af; }
.one > div { flex: 1; }
.auto > div { flex: auto; }
HTML
<div class="c one"><div id="o1"><i style="width: 40px"></i></div><div id="o2"><i style="width: 160px"></i></div></div>
<div class="c auto"><div id="u1"><i style="width: 40px"></i></div><div id="u2"><i style="width: 160px"></i></div></div>
Resultlive, in your browser
What’s happening
  1. Each item holds content of 40px or 160px (the dark bars).
  2. flex: 1 sets the basis to 0%. Both bases are 0, so all 600px are free and split 1 : 1: 300px each, regardless of content.
  3. flex: auto keeps flex-basis: auto, so the bases are the content widths: 40 + 160 = 200px. The free space is 400px, split 1 : 1, 200px each: 40 + 200 = 240px and 160 + 200 = 360px.
  4. The computed longhands confirm the shorthand: 1 1 0% and 1 1 auto.
  5. Use flex: 1 for equal columns, flex: auto for "share the spare room but respect content" (tabs, toolbar buttons), and flex: none for things that must not resize (icons, avatars).
AdvancedEvery shorthand form, as Chrome computes it
The flex shorthand expanded
CSS
.c { display: flex; width: 300px; margin-bottom: 4px; background: #f1f5f9; font-size: 12px; }
.c > div { background: #fde68a; padding: 2px 6px; white-space: nowrap; }
HTML
<div class="c"><div id="d0">(no flex)</div></div>
<div class="c"><div id="d1" style="flex: initial">flex: initial</div></div>
<div class="c"><div id="d2" style="flex: auto">flex: auto</div></div>
<div class="c"><div id="d3" style="flex: none">flex: none</div></div>
<div class="c"><div id="d4" style="flex: 1">flex: 1</div></div>
<div class="c"><div id="d5" style="flex: 2">flex: 2</div></div>
<div class="c"><div id="d6" style="flex: 0">flex: 0</div></div>
<div class="c"><div id="d7" style="flex: 100px">flex: 100px</div></div>
<div class="c"><div id="d8" style="flex: 2 3">flex: 2 3</div></div>
<div class="c"><div id="d9" style="flex: 1 30%">flex: 1 30%</div></div>
Resultlive, in your browser
What’s happening
  1. With no flex at all, an item is 0 1 auto: it never grows, may shrink, and starts from its width or content. flex: initial is the same thing written out.
  2. The keyword forms: auto is 1 1 auto, none is 0 0 auto.
  3. A single unitless number is the grow factor, and the basis becomes 0% (not auto): 1 → 1 1 0%, 2 → 2 1 0%, 0 → 0 1 0%.
  4. A single length is the basis, and grow becomes 1: 100px → 1 1 100px. Two numbers are grow and shrink (2 3 → 2 3 0%); a number and a length are grow and basis (1 30% → 1 1 30%).
  5. All of these are the computed values Chrome 155 reports, so this is the table to recite. In the live result each item sits alone in a 300px row, so you can also see the effect: the forms that grow fill the row, the ones that don't stay at their text width.
AdvancedGotcha: flex: 1 in an auto-height column
flex: 1 in an auto-height and a fixed-height column
CSS
.col { display: flex; flex-direction: column; width: 120px; background: #f1f5f9; margin-bottom: 10px; }
.col > div { flex: 1; background: #fde68a; box-shadow: inset 0 0 0 1px #f1f5f9; }
.col b { display: block; }
.fixed { height: 200px; }
HTML
<div class="col" id="auto"><div><b style="height: 20px"></b></div><div><b style="height: 60px"></b></div></div>
<div class="col fixed" id="fixed"><div><b style="height: 20px"></b></div><div><b style="height: 60px"></b></div></div>
Resultlive, in your browser
What’s happening
  1. In #auto the column has no height, so 0% can't be resolved; the basis falls back to the content size, and with no free space to share, the items stay at 20 and 60px.
  2. In #fixed the column is 200px tall. The 0% basis resolves to 0, all 200px are free, and flex: 1 splits them equally: 100 and 100px.
  3. This is why a "full-height layout" needs a definite height somewhere up the chain (height: 100dvh, or a parent flex container with a set height).
flex-grow: 1 vs flex: 1

Two items in a 600px row contain 40px and 160px of content. Case A gives both flex-grow: 1; case B gives both flex: 1. How wide are they?

Show answer

A: 240px and 360px. B: 300px and 300px. flex-grow: 1 alone leaves flex-basis: auto, so it behaves like flex: auto; flex: 1 also sets the basis to 0%.

CSS
.c { display: flex; width: 600px; margin-bottom: 8px; background: #f1f5f9; }
.c > div { background: #c4b5fd; box-shadow: inset 0 0 0 1px #f1f5f9; }
.c i { display: inline-block; vertical-align: top; height: 30px; background: #5b21b6; }
.a > div { flex-grow: 1; }
.b > div { flex: 1; }
HTML
<div class="c a"><div><i style="width: 40px"></i></div><div><i style="width: 160px"></i></div></div>
<div class="c b"><div><i style="width: 40px"></i></div><div><i style="width: 160px"></i></div></div>
Resultlive, in your browser
What’s happening
  1. Case A: only flex-grow changes; flex-basis stays auto, so the bases are the content sizes, 40 and 160px. Free space is 600 − 200 = 400px, 200px each: 240 and 360px.
  2. Case B: flex: 1 is 1 1 0%, so both bases are 0 and all 600px are split equally: 300 and 300px.
  3. So "make them equal" needs flex: 1 (or flex-basis: 0 explicitly), not just flex-grow: 1.
  4. Even flex: 1 columns stop being equal when one contains something wider than its share that can't shrink, like a long URL: the automatic minimum size kicks in (see Why flex items overflow).

#Wrapping and gap

Added

By default a flex container is single-line: items shrink to fit, and if they can't, they overflow. flex-wrap: wrap lets items move onto new lines when the next one doesn't fit. The decision uses each item's hypothetical size (its basis, clamped by min/max), before any growing: items are placed on a line until the next one would overflow it, and then each line is flexed on its own, as if it were a separate single-line container.

gap (row-gap and column-gap) puts fixed space between items and between lines, never at the outer edges. It replaced the old trick of putting margins on every item and a negative margin on the container to cancel the outer ones. gap in flex layout works in every current browser (Safari was the last, in version 14.1, in 2021).

The mental model: wrapping is filling shelves with books by their spine width; once a shelf is full, start the next shelf, and only then let the books on each shelf lean (grow) to fill it. That's why the last row so often looks different from the others.

Five flex: 1 1 100px items in 340px, gap 10px
CSS
.c { display: flex; flex-wrap: wrap; gap: 10px; width: 340px; background: #f1f5f9; }
.c > div { flex: 1 1 100px; height: 40px; background: #bbf7d0; }
HTML
<div class="c"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div></div>
Resultlive, in your browser
What’s happening
  1. Each item's hypothetical size is its 100px basis. Line 1: three items need 3 × 100 + 2 × 10 = 320px ≤ 340px; a fourth would need 430px, so it wraps.
  2. Line 1 is then flexed on its own: free space 340 − 320 = 20px, split three ways: 100 + 6.67 ≈ 106.67px each.
  3. Line 2 holds items 4 and 5: 2 × 100 + 10 = 210px, free space 130px, so each grows by 65px to 165px. The last line's items are much wider than the first line's: each line flexes independently.
  4. gap: 10px sets both gaps: 10px between items on a line and 10px between the lines, so line 2 starts at 40 + 10 = 50px. The container is 40 + 10 + 40 = 90px tall. No gap appears at the outer edges.
  5. If you want the last row to keep the same column widths, you want a two-dimensional layout: use grid with repeat(auto-fill, minmax(100px, 1fr)) (see repeat(), minmax() and auto-fill vs auto-fit).
Advancedwrap-reverse and different row and column gaps
wrap-reverse with gap: 20px 6px
CSS
.c { display: flex; flex-wrap: wrap-reverse; gap: 20px 6px; width: 300px; background: #f1f5f9; }
.c > div { flex: none; width: 90px; height: 30px; background: #fde68a; }
HTML
<div class="c"><div>1</div><div>2</div><div>3</div><div>4</div></div>
Resultlive, in your browser
What’s happening
  1. gap: 20px 6px means row-gap: 20px (between lines) and column-gap: 6px (between items on a line), in that order, like margin shorthands.
  2. Three 90px items and two 6px gaps take 282px of 300px; the fourth wraps.
  3. wrap-reverse stacks lines from the cross-end: the first line (items 1–3) is at the bottom (50px down) and the second line (item 4) at the top. Items within a line keep their order.
  4. Between the lines there are exactly 20px: the row gap.
How many items per row?

Items are flex: 0 0 150px in a 500px wrapping container. How many fit on a row with gap: 20px? With gap: 30px?

Show answer

Three with a 20px gap, two with a 30px gap.

CSS
.c { display: flex; flex-wrap: wrap; width: 500px; background: #f1f5f9; margin-bottom: 10px; }
.c > div { flex: 0 0 150px; height: 20px; background: #93c5fd; }
HTML
<div class="c" id="g20" style="gap: 20px"><div></div><div></div><div></div><div></div></div>
<div class="c" id="g30" style="gap: 30px"><div></div><div></div><div></div><div></div></div>
Resultlive, in your browser
What’s happening
  1. n items fit if n × 150 + (n − 1) × gap ≤ 500.
  2. Gap 20px: three items need 450 + 40 = 490px ≤ 500 → 3 per row (a fourth would need 660px).
  3. Gap 30px: three items need 450 + 60 = 510px > 500 → only 2 per row.
  4. Gaps count when deciding where to wrap, which is why adding a bigger gap can suddenly drop a column on the same screen.

#Why flex items overflow: min-width auto

Added

Flex items have a special default: min-width: auto (or min-height: auto in a column). For a flex item, auto resolves to a content-based minimum: roughly the item's min-content width, the width of its longest word, widest image, or unbreakable line. The item will shrink, but never below that. Normally that's helpful (text doesn't get crushed); but put a long URL, a <pre> block, a wide table or a white-space: nowrap title inside a flex: 1 column, and the column refuses to shrink: it pushes past the container and the whole layout overflows.

The fix is to tell that item it may shrink below its content: min-width: 0 (or min-height: 0 in a column), or give it an overflow other than visible, which also makes the automatic minimum 0. The content then overflows inside the item, where you can clip it, scroll it or truncate it with an ellipsis.

The mental model: by default a flex item is a suitcase that won't close on its contents. min-width: 0 says "close it anyway"; what sticks out is then the contents' problem, not the layout's.

A 400px unbreakable thing in a flex: 1 column
CSS
.row { display: flex; width: 300px; margin-bottom: 8px; background: #f1f5f9; outline: 2px solid #64748b; }
.side { flex: none; width: 100px; background: #fde68a; }
.main { flex: 1; background: #dbeafe; }
.long { display: inline-block; vertical-align: top; width: 400px; height: 24px; background: repeating-linear-gradient(90deg, #93c5fd 0 20px, #bfdbfe 20px 40px); }
.fix .main { min-width: 0; }
.ovf .main { overflow: hidden; }
HTML
<div class="row" id="r1"><div class="side">side</div><div class="main"><span class="long"></span></div></div>
<div class="row fix" id="r2"><div class="side">side</div><div class="main"><span class="long"></span></div></div>
<div class="row ovf" id="r3"><div class="side">side</div><div class="main"><span class="long"></span></div></div>
Resultlive, in your browser
What’s happening
  1. Each row is 300px: a rigid 100px side and a flex: 1 main column, so main "should" be 200px. Inside main is a 400px inline-block that can't break.
  2. Row 1: main's min-width is auto, which for a flex item means "no smaller than my min-content width", 400px. The flex algorithm wants 200px, the minimum says 400px, and the minimum wins: main is 400px and sticks out of the 300px row by 200px. On a real page that's the horizontal scrollbar on phones.
  3. Row 2: min-width: 0 removes the content-based minimum. Main gets its 200px, and the 400px child overflows the item instead (by 200px), where you can deal with it.
  4. Row 3: overflow: hidden on the item also makes the automatic minimum 0 (it only applies to items whose overflow is visible). Main is 200px and the overflow is clipped: just past the row's right edge, elementFromPoint no longer finds the striped child.
  5. The rule to remember: if a flex item contains something that shouldn't grow the layout, give the item min-width: 0. It is the single most common flexbox bug fix.
AdvancedTruncating with an ellipsis inside a flex item

text-overflow: ellipsis needs a box with a fixed width to truncate against. Inside a flex row, the box that needs min-width: 0 is the flex item, often one level above the element with the ellipsis.

The ellipsis that doesn't work, and the one that does
CSS
.card { display: flex; gap: 10px; align-items: center; width: 300px; padding: 8px; margin-bottom: 8px; background: #f1f5f9; box-sizing: border-box; }
.avatar { flex: none; width: 40px; height: 40px; border-radius: 50%; background: #c4b5fd; }
.text { flex: 1; }
.title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fixed .text { min-width: 0; }
HTML
<div class="card" id="bad"><div class="avatar"></div><div class="text"><div class="title">A very long conversation title that keeps going and going well past the card</div></div></div>
<div class="card fixed" id="good"><div class="avatar"></div><div class="text"><div class="title">A very long conversation title that keeps going and going well past the card</div></div></div>
Resultlive, in your browser
What’s happening
  1. .title has everything an ellipsis needs: white-space: nowrap, overflow: hidden, text-overflow: ellipsis. But its width comes from its parent, the .text flex item.
  2. In #bad, .text has the default min-width: auto; its min-content width is the whole unbreakable title, so it grows past the card. The title is as wide as its text, nothing overflows it, and no ellipsis appears (its scrollWidth isn't larger than its clientWidth).
  3. In #good, min-width: 0 on .text lets it shrink to its share: 300 − 2 × 8 (padding) − 40 (avatar) − 10 (gap) = 234px. Now the title is narrower than its text, so it's clipped with an ellipsis (scrollWidth > clientWidth).
  4. Put min-width: 0 on the flex item, not on the element with the ellipsis.
AdvancedColumns: min-height auto and stuck scroll panels

The same rule in a column direction is min-height: auto, and it causes the classic "my scrolling panel grows instead of scrolling" bug in full-height app layouts, usually with a nested flex column between the fixed-height shell and the scrolling list.

A list that should scroll inside a 200px app
CSS
.app { display: flex; flex-direction: column; height: 200px; width: 200px; margin-bottom: 10px; background: #f1f5f9; outline: 2px solid #64748b; }
.head { flex: none; height: 40px; background: #93c5fd; }
.body { flex: 1; display: flex; flex-direction: column; }
.list { flex: 1; overflow: auto; background: #dbeafe; }
.list i { display: block; height: 500px; background: linear-gradient(#dbeafe, #93c5fd); }
.fix .body { min-height: 0; }
HTML
<div class="app" id="bad"><div class="head">header</div><div class="body"><div class="list"><i></i></div></div></div>
<div class="app fix" id="good"><div class="head">header</div><div class="body"><div class="list"><i></i></div></div></div>
Resultlive, in your browser
What’s happening
  1. The app is a 200px column: a 40px header and a flex: 1 body, which is itself a flex column holding a flex: 1; overflow: auto list with 500px of content.
  2. In #bad, .body is a flex item with min-height: auto, and its content (the list) is 500px tall, so its minimum is 500px. It ignores its 160px share and grows to 500px, the list grows with it, and the list never scrolls: its clientHeight equals its scrollHeight, 500px. The app overflows by 340px.
  3. The list's own overflow: auto doesn't help, because the problem is one level up: .body has overflow: visible, so its automatic minimum still applies.
  4. In #good, min-height: 0 on .body lets it be its 160px share, the list fills it, and now the list scrolls: 160px visible of 500px.
  5. In a deep flex layout, every flex item between the fixed-size ancestor and the scroller needs min-height: 0 (or overflow: hidden).
Why is the page wider than the phone?

A chat layout: .layout { display: flex }, .sidebar { width: 80px; flex: none }, .messages { flex: 1 }. One message contains a 600px code block (<pre>, with overflow-x: auto on the pre). On a 360px phone the whole page scrolls sideways. Why, and what's the one-line fix?

Show answer

.messages has min-width: auto, so it won't shrink below the <pre>'s 600px min-content width. Add min-width: 0 to .messages; then the pre is constrained to the column and its own overflow-x: auto scrolls it.

What’s happening
  1. flex: 1 means "share the space", but flex items also have an automatic minimum width: their min-content width, unless their overflow isn't visible.
  2. A <pre> doesn't wrap, so the message column's min-content width is about the code block's 600px. .messages becomes ~600px, the row becomes ~680px, and the page overflows the 360px viewport.
  3. The pre's overflow-x: auto never kicks in, because the pre is given all the width it wants; nothing is constraining it.
  4. min-width: 0 on .messages lets it shrink to 360 − 80 = 280px; the pre is now 280px wide and scrolls inside. overflow: hidden on .messages would also work, but would clip things like focus rings and shadows.

#Common flexbox layouts

Added

Most real flexbox code is a handful of patterns, each a few lines: a navbar with items pushed apart, a sticky footer, cards whose buttons line up, an input with a button, and a sidebar that wraps under the content on small screens. The tools are always the same: flex: 1 for the part that takes the rest, flex: none for the parts that mustn't resize, margin-left: auto (or margin-top: auto) to push things apart, and gap for spacing.

The mental model for auto margins in flex: an auto margin is a spring. It soaks up all the free space on its side, so margin-left: auto on one item pushes it, and everything after it, to the far end. Two springs on both sides centre the item.

A navbar: logo left, links right, all centred vertically
CSS
.nav { display: flex; align-items: center; gap: 12px; width: 500px; height: 50px; padding: 0 12px; box-sizing: border-box; background: #1f2937; }
.logo { width: 80px; height: 30px; background: #fde68a; }
.nav a { width: 60px; height: 24px; display: grid; place-items: center; background: #374151; color: #f9fafb; font-size: 12px; text-decoration: none; }
.push { margin-left: auto; }
HTML
<nav class="nav">
  <div class="logo"></div>
  <a href="#" class="push">Docs</a><a href="#">Blog</a><a href="#">Login</a>
</nav>
Resultlive, in your browser
What’s happening
  1. align-items: center centres the 30px logo and the 24px links in the 50px bar.
  2. margin-left: auto on the first link is the spring. Free space is 500 − 24 (padding) − 80 − 3 × 60 − 3 × 12 (gaps) = 180px, and the auto margin takes all of it.
  3. So the distance from the logo to the first link is the 180px margin plus the 12px gap, 192px measured, and the last link ends exactly at the 12px padding.
  4. Why not justify-content: space-between? It would spread all the items apart (logo, Docs, Blog, Login evenly). The auto margin splits them into two groups, and it keeps working if you add a link. For a second group in the middle, give it auto margins on both sides.
AdvancedCards with their buttons lined up
Equal-height cards, button pushed to the bottom
CSS
.cards { display: flex; gap: 10px; width: 480px; }
.card { flex: 1; display: flex; flex-direction: column; padding: 10px; background: #f1f5f9; }
.body { background: #dbeafe; }
.btn { margin-top: auto; height: 28px; background: #1f2937; color: #fff; display: grid; place-items: center; font-size: 12px; }
HTML
<div class="cards">
  <div class="card"><div class="body" style="height: 40px">short</div><div class="btn">Buy</div></div>
  <div class="card"><div class="body" style="height: 80px">medium</div><div class="btn">Buy</div></div>
  <div class="card"><div class="body" style="height: 120px">long</div><div class="btn">Buy</div></div>
</div>
Resultlive, in your browser
What’s happening
  1. The outer row stretches the three cards to the height of the tallest, 10 + 120 + 28 + 10 = 168px (the default cross-axis stretch).
  2. Each card is itself a flex column. In the shorter cards there's free space below the body, and the button's margin-top: auto (a spring) takes all of it, so every button sits at the bottom of its card: 130px from the top in all three.
  3. flex: 1 on the cards gives equal widths: (480 − 2 × 10) / 3 ≈ 153.3px each. With a 0 basis, the padding is added to the basis before sharing, and since all three have the same padding, they come out equal.
  4. Without margin-top: auto, each button would sit right under its body text, at three different heights.
AdvancedInput that fills the rest of a row
Input + button
CSS
.row { display: flex; gap: 8px; width: 200px; margin-bottom: 8px; }
.row input { flex: 1; }
.row button { flex: none; width: 80px; }
.fix input { min-width: 0; }
HTML
<form class="row" id="bad"><input placeholder="Search"><button type="submit">Go</button></form>
<form class="row fix" id="good"><input placeholder="Search"><button type="submit">Go</button></form>
Resultlive, in your browser
What’s happening
  1. The button is rigid (flex: none; width: 80px) and the input takes the rest with flex: 1. The input's share should be 200 − 8 − 80 = 112px.
  2. In #bad the input is wider than that (about 150px in this test run, its default width from size="20", which depends on the font) and the button is pushed past the row's edge. Inputs have an intrinsic width, and as flex items their automatic minimum keeps them from shrinking below it.
  3. In #good, min-width: 0 lets the input shrink to exactly 112px and the button ends at the row's edge.
  4. The same min-width: 0 rule as in Why flex items overflow, and the reason so many design systems put min-width: 0 on their input components.
AdvancedSidebar that wraps without a media query

Flexbox can be responsive without a media query: give both parts a basis and let them wrap when the two bases don't fit side by side.

Sidebar and main, with no media query
CSS
.layout { display: flex; flex-wrap: wrap; gap: 16px; }
.side { flex: 1 1 160px; height: 60px; background: #fde68a; }
.main { flex: 3 1 320px; height: 60px; background: #dbeafe; }
HTML
<div class="layout"><aside class="side">sidebar</aside><main class="main">main</main></div>
Resultlive, in your browser
What’s happening
  1. The two bases plus the gap are 160 + 16 + 320 = 496px.
  2. At a 360px viewport the layout is 336px wide (12px body margins). 496 doesn't fit, so main wraps onto its own line, 16px (the row gap) below the sidebar. Each item is alone on its line and grows to the full 336px.
  3. At 800px the layout is 776px. Both fit, leaving 776 − 496 = 280px free, shared 1 : 3: the sidebar grows by 70px to 230px and main by 210px to 530px.
  4. The breakpoint isn't written anywhere: it's wherever the bases stop fitting. The test runs at both widths; resize the window to watch the live result switch. For layouts that must switch at exact widths, use media or container queries (see Container queries).

Written fresh for interviews (there was no Google Doc for this one). Every live example was checked in headless Chrome 155 by scripts/check-demos.mjs.

RohitDownloads · All chapters

Sync progress across devices

Type the same private phrase on your Mac and your phone, and your Learned ticks follow you between them — on every study site.

The phrase never leaves this device: only a fingerprint of it is sent, and the server stores a fingerprint of that. Anyone who knows the phrase could see or change your ticks, so pick something you don’t use elsewhere.

Sync is on in this browser.

Sync ID

This ID must be the same on every device. If another device shows a different one, its phrase is different (capital letters count): tap “Turn off here” on it and type the phrase again exactly.