← Back to Index

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:

  1. Determine each item's base size (flex-basis)
  2. Calculate free space (container size - sum of base sizes)
  3. 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

1
2
3

column - main axis is vertical

1
2
3
.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:

flex-direction: row (default) main axis (horizontal) cross axis (vertical) justify-content → left/right align-items → top/bottom flex-direction: column cross axis (horizontal) main axis (vertical) justify-content → top/bottom (swapped!) align-items → left/right (swapped!) The rule: justify-content = distribute along the main axis align-items = position along the cross axis

ROW: justify-content: center → centers horizontally

A
B
C

COLUMN: justify-content: center → centers vertically (main axis is now vertical)

A
B
C
/* 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)

A
B
C

center

A
B
C

flex-end

A
B
C

space-between

A
B
C

space-around

A
B
C

space-evenly

A
B
C
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

A
B
C

flex-start - items at top

A
B
C

center - items centered vertically

A
B
C

flex-end - items at bottom

A
B
C
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)

basis: 100px
basis: 200px
basis: auto
.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

grow: 0
grow: 0
grow: 0

flex-grow: 1 on all - free space divided equally

grow: 1
grow: 1
grow: 1

flex-grow: 1, 2, 1 - middle item gets double the extra space

grow: 1
grow: 2
grow: 1
/* 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).

Ad

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)

shrink: 1
shrink: 0
shrink: 1
/* 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: 1
flex: 2
flex: 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

Item 1
Item 2
Item 3
Item 4
Item 5
.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

A
B
C
D
.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

Short
This text is very long and refuses to shrink below its natural content width
Short

Fixed: min-width: 0 + overflow: hidden

Short
This text is very long but now truncates because min-width: 0 allows shrinking
Short
/* 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

Header
Main content (flex: 1 fills remaining space)
Footer (pushed to bottom)
.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

Sidebar (flex: 0 0 120px)
Main content (flex: 1)
.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

Centered!
.container {
  display: flex;
  justify-content: center;  /* horizontal */
  align-items: center;      /* vertical */
}

Retrieval check

Question 1

What does flex-grow distribute?

The total container width equally
The free space left after all items reach their flex-basis
The difference between min-width and max-width
The gap between items proportionally

Question 2

What's the difference between flex: 1 and flex: 1 1 auto?

No difference - they're equivalent
flex: 1 sets basis to 0% (equal widths); flex: 1 1 auto uses content size as basis
flex: 1 prevents shrinking; flex: 1 1 auto allows it
flex: 1 only works in row direction

Question 3

A flex item with long text overflows its container despite flex-shrink: 1. What's the most likely fix?

Set flex-shrink: 99 to force it smaller
Add overflow: scroll to the container
Add min-width: 0 to the item
Set width: 0 on the item

Question 4

You want a fixed 250px sidebar that never grows or shrinks. What flex value?

flex: 1 0 250px
flex: 0 0 250px
flex: 250px
flex: none 250px

Question 5

What does justify-content: space-between do with 3 items?

Equal gaps everywhere including edges
First item at start, last at end, equal gaps between them
All three items centered with gaps around them
Items spread with half-gaps at the edges