Lesson 8
Flexbox - How the Algorithm Works
You've used Flexbox. But do you know why items end up the size they do? This lesson covers the mental model behind the flex algorithm - what actually happens when items compete for space.
The core idea
A flex container distributes space along a main axis. The algorithm runs in three steps:
- Determine each item's base size (
flex-basis) - Calculate free space (container size - sum of base sizes)
- Distribute free space (if positive →
flex-grow, if negative →flex-shrink)
Everything in Flexbox is about this question: "There's X pixels of space to distribute (or X pixels of overflow to absorb). How do we divide it among the items?"
flex-direction - choosing the main axis
row (default) - main axis is horizontal
column - main axis is vertical
.row { display: flex; flex-direction: row; } /* default */
.column { display: flex; flex-direction: column; }
justify-content vs align-items - it's about axes, not directions
The common confusion: "justify is horizontal, align is vertical." That's only true in flex-direction: row. The real rule:
justify-content→ distributes space along the main axis (the direction items flow)align-items→ positions items along the cross axis (perpendicular to the flow)
ROW: justify-content: center → centers horizontally
COLUMN: justify-content: center → centers vertically (main axis is now vertical)
/* Row: justify-content works horizontally */
.row { display: flex; flex-direction: row; justify-content: center; }
/* Column: justify-content works VERTICALLY */
.col { display: flex; flex-direction: column; justify-content: center; }
Mental model: "justify" = "where along the line do items sit?" / "align" = "how do items behave perpendicular to that line?" These definitions hold regardless of direction.
justify-content - all values demonstrated
flex-start (default)
center
flex-end
space-between
space-around
space-evenly
justify-content: flex-start; /* pack to start */
justify-content: center; /* center items */
justify-content: flex-end; /* pack to end */
justify-content: space-between; /* first item at start, last at end, equal gaps between */
justify-content: space-around; /* equal space around each item (half-size at edges) */
justify-content: space-evenly; /* truly equal gaps everywhere */
align-items - positioning on the cross axis
stretch (default) - items fill the cross axis
flex-start - items at top
center - items centered vertically
flex-end - items at bottom
align-items: stretch; /* fill height (default) */
align-items: flex-start; /* top of container */
align-items: center; /* vertically centered */
align-items: flex-end; /* bottom of container */
align-items: baseline; /* align text baselines */
The classic centering trick: display: flex; justify-content: center; align-items: center; - centers both axes. That's it. Works on any container.
flex-basis - the starting size
flex-basis defines an item's ideal size before growing or shrinking. It's the answer to "how big would this be if there were no space pressure?"
Three items: 100px, 200px, auto (auto = content width)
.item-1 { flex-basis: 100px; } /* exactly 100px as starting point */
.item-2 { flex-basis: 200px; } /* exactly 200px */
.item-3 { flex-basis: auto; } /* use width/content size */
flex-basis vs width: They look similar but differ. flex-basis is the starting point for the flex algorithm. width is a hard constraint (in non-flex contexts). In a flex container, flex-basis takes priority over width unless flex-basis is auto (then it falls back to width). Use flex-basis in flex layouts, not width.
flex-grow - distributing extra space
When the total of all items' flex-basis values is less than the container width, there's leftover space. flex-grow decides how to distribute it.
The algorithm: calculate free space, then divide it proportionally by each item's grow factor.
flex-grow: 0 (default) - items stay at their basis, free space is unused
flex-grow: 1 on all - free space divided equally
flex-grow: 1, 2, 1 - middle item gets double the extra space
/* No growth - items stay at flex-basis */
.item { flex-grow: 0; }
/* Equal growth - each gets same share of free space */
.item { flex-grow: 1; }
/* Proportional: item B gets 2/(1+2+1) = 50% of free space */
.item-a { flex-grow: 1; }
.item-b { flex-grow: 2; }
.item-c { flex-grow: 1; }
Critical detail: flex-grow distributes the free space, not the total width. If items have different basis sizes, grow: 1 on each means they each get the same extra space - they don't become equal width. For equal-width items, use flex: 1 (which sets basis to 0).
flex-shrink - absorbing overflow
When the total of all items' flex-basis values is more than the container width, there's overflow. flex-shrink decides how much each item gives up.
Three items with flex-basis: 250px in a narrow container. Middle item has shrink: 0 (refuses to shrink)
/* All items shrink equally (default) */
.item { flex-shrink: 1; }
/* This item won't shrink - others absorb all the overflow */
.item-fixed { flex-shrink: 0; }
/* This item shrinks twice as fast as others */
.item-flexible { flex-shrink: 2; }
Shrink is weighted by basis size. The actual amount each item shrinks is: (shrink-factor × flex-basis) / sum(all shrink × basis) × overflow. This means larger items give up more pixels even at the same shrink value - which usually feels fair.
The flex shorthand - the three values together
flex is shorthand for flex-grow, flex-shrink, and flex-basis:
flex: 1 / flex: 2 / flex: 1 - basis is 0, so total space is divided 1:2:1
flex: <grow> <shrink> <basis>;
/* Common patterns: */
flex: 0 1 auto; /* default - don't grow, can shrink, basis is content/width */
flex: 1; /* same as flex: 1 1 0% - grow equally, basis is zero */
flex: 1 0 200px; /* grow from 200px, never shrink below it */
flex: none; /* same as flex: 0 0 auto - rigid, no flex at all */
flex: auto; /* same as flex: 1 1 auto - grow and shrink from content size */
The most important thing about the shorthand: flex: 1 sets basis to 0%, not auto. This is why flex: 1 makes items equal width - they all start from 0 and grow equally. flex: 1 1 auto (or flex: auto) starts from content size, so items with more content end up wider.
flex-wrap - handling overflow gracefully
By default, flex items squeeze onto one line (even if they overflow). flex-wrap: wrap lets them break into new lines:
flex-wrap: wrap with flex: 1 1 150px - items wrap when they can't fit at 150px minimum
.container {
display: flex;
flex-wrap: wrap;
}
.item {
flex: 1 1 150px;
/* Means: grow to fill space, shrink if needed, but start at 150px.
When 150px × items > container, items wrap to a new line. */
}
The responsive card pattern: flex: 1 1 <min-width> is the simplest responsive layout without media queries. Items fill the row, and when the row can't fit them at their basis, they wrap. Combined with gap, this handles most card grids.
gap - spacing between items
gap: 1rem - consistent spacing without margin hacks
.container {
display: flex;
gap: 1rem; /* both row and column gap */
/* or: */
row-gap: 1rem; /* vertical gap (between wrapped rows) */
column-gap: 0.5rem; /* horizontal gap (between items in a row) */
}
gap replaced the old pattern of margin-right on items with a :last-child override. It only creates space between items, never on the edges.
The min-width: auto gotcha
This is the single most common Flexbox bug. By default, flex items have min-width: auto, which means they will never shrink below their content size. If an item has a long word or a white-space: nowrap, it can overflow the container even with flex-shrink: 1.
Broken: long text overflows because min-width: auto prevents shrinking below content
Fixed: min-width: 0 + overflow: hidden
/* The problem: */
.item {
flex: 1;
white-space: nowrap;
/* min-width defaults to auto → won't shrink below content → overflows */
}
/* The fix: */
.item {
flex: 1;
min-width: 0; /* allow shrinking below content size */
overflow: hidden; /* clip or truncate the overflow */
text-overflow: ellipsis; /* show ... for truncated text */
white-space: nowrap;
}
Rule of thumb: Whenever a flex item contains text that might overflow, or a child element with a fixed/intrinsic width (images, tables, pre blocks), add min-width: 0 to the flex item. Same applies to min-height: 0 in column layouts.
The algorithm step by step
To summarise how the browser resolves flex item sizes:
1. Set each item's "hypothetical main size" = flex-basis (or width if basis is auto)
2. Sum all hypothetical sizes + gaps
3. Compare to container size:
- If sum < container → FREE SPACE exists → distribute via flex-grow
- If sum > container → OVERFLOW exists → absorb via flex-shrink
- If sum = container → perfect fit, do nothing
4. Apply min-width/max-width constraints
- If an item hits min-width, it stops shrinking
- Remaining overflow is redistributed to other items
5. Position items via justify-content and align-items
Step 4 is why layouts sometimes "break". You set flex-shrink: 1 on everything, but an item refuses to shrink because its min-width (auto = content size) prevents it. Fix: min-width: 0.
Practical patterns
Sticky footer
Content pushes footer down; footer sticks to bottom if content is short
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.header { /* natural height */ }
.main { flex: 1; } /* takes all remaining space */
.footer { /* natural height, always at bottom */ }
Sidebar + content layout
Fixed sidebar, flexible main content
.layout {
display: flex;
gap: 1rem;
}
.sidebar { flex: 0 0 250px; } /* don't grow, don't shrink, fixed at 250px */
.main { flex: 1; } /* fills remaining space */
Centering anything
Perfectly centered - horizontally and vertically
.container {
display: flex;
justify-content: center; /* horizontal */
align-items: center; /* vertical */
}
Retrieval check
Question 1
What does flex-grow distribute?
Question 2
What's the difference between flex: 1 and flex: 1 1 auto?
Question 3
A flex item with long text overflows its container despite flex-shrink: 1. What's the most likely fix?
Question 4
You want a fixed 250px sidebar that never grows or shrinks. What flex value?
Question 5
What does justify-content: space-between do with 3 items?