Deep dive: Flexbox
Chapter 5 — Deep Dive: Flexbox
Flexbox is a one-dimensional layout system — it distributes items along a single axis. It excels at navigation bars, toolbars, card rows, form rows, and any situation where you need items to share space intelligently. This chapter covers every container and item property, demystifies flex-grow / flex-shrink / flex-basis, and ends with a library of production-ready patterns.
1. The Two Axes
Every flexbox layout has two perpendicular axes. Which one is which depends entirely
on flex-direction. Getting this mental model right makes the alignment
properties click immediately.
main axis →
item 1
item 2
item 3
cross axis ↓
With
With
flex-direction: row (default): main axis = horizontal (→), cross axis = vertical (↓).With
flex-direction: column: axes swap — main = vertical (↓), cross = horizontal (→).
This is the single most important flexbox concept.
justify-content always controls the main axis.
align-items always controls the cross axis. If you get
confused about which property does what, ask: "which direction is my main axis
right now?" — then the property names make sense.
2. Container Properties
Set these on the flex container (the parent with display: flex).
Direction and wrapping
flex-direction
row · row-reverse · column · column-reverse
Sets the main axis. row (default) = left→right. column = top→bottom. The
-reverse variants flip item order visually but do not change DOM order — keyboard navigation still follows DOM order.flex-wrap
nowrap · wrap · wrap-reverse
nowrap (default) — all items stay on one line, may overflow or shrink. wrap — items that don't fit start a new line. wrap-reverse — new lines are added above rather than below.
flex-flow
[ direction ] [ wrap ]
Shorthand for flex-direction + flex-wrap. Example:
flex-flow: row wrap. Write them separately for clarity in most codebases.Main axis alignment (justify-*)
justify-content
flex-start · flex-end · center · space-between · space-around · space-evenly · start · end
Distributes free space along the main axis after items are sized. Has no effect when items overflow or when a flex item has
flex-grow consuming all free space.justify-items
normal (ignored in flex)
Has no effect in flexbox — it only applies to Grid. Don't confuse with justify-content. A common mistake.
Cross axis alignment (align-*)
align-items
stretch · flex-start · flex-end · center · baseline · first baseline · last baseline
Aligns items along the cross axis within their line. stretch (default) makes all items the same cross-axis size. baseline aligns the text baselines — great for mixed font-size nav items.
align-content
stretch · flex-start · flex-end · center · space-between · space-around · space-evenly
Distributes space between wrapped lines on the cross axis. Has no effect when there is only one line (no wrapping, or all items fit). Requires flex-wrap: wrap.
Gaps
gap
[ row-gap ] [ column-gap ]
Sets space between flex items. Does not add space at the edges — only between items. Replaced the old margin-based hack. One value sets both; two values set row-gap then column-gap.
row-gap / column-gap
length · percentage
The longhand components of gap. In row flex-direction, column-gap is the between-item space and row-gap controls space between wrapped lines.
3. Item Properties
Set these on the flex items (the direct children of the flex container).
The flex sizing triad
flex-basis
auto · content · length · percentage
The starting size of the item along the main axis before free space is distributed. auto = use the item's width/height. content = size to content. Takes precedence over width/height when both are set.
flex-grow
0 (default) · positive number
How much of the remaining free space the item absorbs. The value is a ratio — flex-grow: 2 on one item and 1 on another means the first gets twice the free space. Zero means the item does not grow.
flex-shrink
1 (default) · 0 · positive number
How much the item shrinks when there is not enough space. Default 1 = all items shrink proportionally. 0 means the item refuses to shrink — it will overflow if needed. Useful for fixed-width sidebars.
flex
[ grow ] [ shrink ] [ basis ]
The shorthand. Always use the shorthand — it sets sensible defaults for omitted values.
flex: 1 = grow: 1, shrink: 1, basis: 0% (not auto). flex: auto = 1 1 auto. flex: none = 0 0 auto (rigid).flex-grow / flex-shrink — how the maths works
Container: 600px wide. Items A, B, C each flex-basis: 100px. Free space = 300px.
A — flex-grow: 1
+100px
+100px
B — flex-grow: 1
+100px
+100px
C — flex-grow: 1
+100px
+100px
Item A has flex-grow: 2, B and C have flex-grow: 1. Total growth units = 4.
A — flex-grow: 2
+150px → 250px
+150px → 250px
B — flex-grow: 1
+75px → 175px
+75px → 175px
C — flex-grow: 1
+75px → 175px
+75px → 175px
flex-shrink: A=0 (won't shrink), B and C shrink normally when container narrows.
A — shrink: 0
stays 200px
stays 200px
B — shrinks
C — shrinks
/* Common flex shorthand values — memorise these */
.item {
flex: 1; /* grow:1 shrink:1 basis:0% — equal share of ALL space */
flex: auto; /* grow:1 shrink:1 basis:auto — grow/shrink from content size */
flex: none; /* grow:0 shrink:0 basis:auto — rigid, sized to content */
flex: 0 0 200px; /* fixed 200px — won't grow OR shrink */
flex: 1 0 200px; /* starts at 200px, can grow, won't shrink */
}
/* flex: 1 vs flex: auto — the key difference */
/* flex: 1 → basis: 0% → items share the TOTAL container width equally */
/* flex: auto → basis: auto → items share only the FREE space; content size matters */
Individual item overrides
align-self
auto · stretch · flex-start · flex-end · center · baseline
Overrides
align-items for a single item. auto inherits the container's align-items value. Use to push one item to the opposite end of the cross axis.order
integer (default: 0)
Changes visual order without altering DOM order. Lower numbers appear first. Accessibility warning: keyboard navigation and screen readers follow DOM order, not visual order. Only use order for purely visual reordering where DOM order makes semantic sense.
4. Alignment Values in Full
justify-content — main axis
flex-start
items packed to start
flex-end
items packed to end
center
items centred
space-between
first/last flush, gaps between
space-around
equal space around each item (half at edges)
space-evenly
equal space everywhere including edges
align-items — cross axis (per line)
stretch
default — fill cross-axis height
flex-start
align to cross-axis start
flex-end
align to cross-axis end
center
centre on cross axis
baseline
align text baselines
first baseline
align to first line's baseline
align-content — cross axis (between lines)
stretch
default — lines fill cross axis
flex-start
lines packed to start
flex-end
lines packed to end
center
lines centred
space-between
first/last flush, space between
space-evenly
equal space everywhere
The place-* shorthands
place-content
align-content + justify-content
place-items
align-items + justify-items
place-self
align-self + justify-self
One value sets both axes equally. Two values: first = block (cross) axis, second = inline (main) axis.
5. Wrapping in Depth
/* flex-wrap: wrap — items flow onto new lines when they don't fit */
.card-row {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.card {
flex: 1 1 280px; /* min 280px, grow to fill, shrink if needed */
/* On a 900px container: 3 cards per row (3×280 + 2×16 = 872px) */
/* On a 600px container: 2 cards per row */
/* On a 320px container: 1 card per row */
}
/* The "stretching last row" problem with flex: 1 */
/* If 5 items wrap to 3+2, the last 2 each take 50% width */
/* Fix: add invisible spacer items, or switch to Grid auto-fit */
.card-row::after {
content: '';
flex: 1 1 280px; /* phantom item prevents last-row stretching */
}
align-content has no effect with a single line. If your flex
container doesn't wrap (or all items fit on one line),
align-content
does nothing — only align-items applies. This is a common source of
confusion when switching between wrapping and non-wrapping containers.
6. Production Patterns
Perfect centring
display: flex;
justify-content: center;
align-items: center;
/* Works for both row and column direction */
Nav bar — logo left, links right
/* container */
display: flex;
align-items: center;
/* push links to the right */
.nav-links { margin-left: auto; }
Sticky footer
body {
display: flex;
flex-direction: column;
min-height: 100dvh;
}
main { flex: 1; } /* grows to fill */
Icon + text alignment
display: inline-flex;
align-items: center;
gap: 0.5em;
/* icon and text share baseline */
Equal-height cards
/* container */
display: flex;
align-items: stretch; /* default */
/* all cards naturally equal height */
/* push CTA to card bottom: */
.card { display: flex;
flex-direction: column; }
.card__cta { margin-top: auto; }
Holy grail layout (flex)
body { display: flex;
flex-direction: column; }
.content-row { display: flex;
flex: 1; }
nav { flex: 0 0 200px; }
main { flex: 1; }
aside { flex: 0 0 160px; }
Responsive nav collapse
nav { display: flex;
flex-wrap: wrap;
gap: 8px; }
nav a { flex: 1 1 120px; }
/* links wrap naturally — no media query needed */
Auto margin trick
/* margin: auto on a flex item absorbs */
/* all remaining free space */
.item { margin-left: auto; }
/* pushes this item to the far right */
.item { margin: auto; }
/* centres this item in all directions */
7. Common Mistakes
/* MISTAKE 1: Setting width on flex items instead of flex-basis */
.item {
width: 200px; /* overridden by flex-basis if both exist */
}
/* Fix: use flex: 0 0 200px instead */
/* MISTAKE 2: Forgetting min-width: 0 on flex items with overflow content */
.item {
flex: 1;
/* flex items have min-width: auto by default */
/* this prevents shrinking below content size */
/* long text or images may overflow the container */
}
.item {
flex: 1;
min-width: 0; /* allows flex item to shrink below content size */
}
/* MISTAKE 3: Using justify-items in flexbox (it's a Grid property) */
.flex-container {
justify-items: center; /* does nothing in flex */
}
/* Fix: use justify-content for main axis, align-items for cross axis */
/* MISTAKE 4: Expecting align-content to work without wrapping */
.flex-container {
flex-wrap: nowrap; /* default */
align-content: space-between; /* ignored — only one line */
}
/* MISTAKE 5: flex: 1 on items inside a container without a height */
.container {
display: flex;
flex-direction: column;
/* no height set — flex: 1 on children can't distribute space they don't have */
}
/* Fix: give the container a height (height: 100dvh, min-height, etc.) */
Chapter Summary
| Concept | Key point |
|---|---|
| Main vs cross axis | justify-* = main axis. align-* = cross axis. Which is which depends on flex-direction. In row: main = horizontal, cross = vertical. |
| flex-grow | Distributes free space as a ratio. 0 = don't grow (default). flex-grow: 2 gets twice the free space of flex-grow: 1. |
| flex-shrink | Controls shrinking when content overflows. 1 = shrink (default). 0 = refuse to shrink (rigid). Weighted by flex-basis. |
| flex-basis | The starting size before grow/shrink apply. auto = use width/height. 0% = ignore content size (used by flex: 1). |
| flex shorthand | flex: 1 → (1 1 0%). flex: auto → (1 1 auto). flex: none → (0 0 auto). Always use shorthand — it sets correct defaults for omitted values. |
| min-width: 0 | Flex items default to min-width: auto, preventing shrink below content size. Add min-width: 0 to items containing text or images that need to shrink. |
| margin: auto | In flexbox, auto margins absorb all remaining free space in that direction. The cleanest way to push one item to the opposite end. |
| align-content | Only works with flex-wrap: wrap and multiple lines. Has zero effect on single-line flex containers. |
| order | Changes visual order only. Keyboard nav and screen readers follow DOM order. Use sparingly and only for non-semantic reordering. |
| justify-items | Does not exist in flexbox — Grid only. Don't confuse with justify-content. |
Exercises
- Flex grow maths: Create a flex container 800px wide with three items: flex-basis 100px each, flex-grow 1, 2, 3. Calculate the final width of each item before opening DevTools to verify.
- Navigation bar: Build a nav bar with a logo on the left, three navigation links in the centre, and a "Sign in" button on the right — using only flexbox and
margin-left: auto(no absolute positioning). - Sticky footer: Create a page where the footer always sits at the bottom of the viewport even when content is short, using
display: flex; flex-direction: columnon the body andflex: 1on main. - Equal-height cards with bottom CTA: Build a row of three cards with varying content lengths. Make all cards equal height and ensure each card's "Read more" button is pinned to the bottom of the card regardless of content length.
- min-width: 0 debugging: Create a flex container with two items: one containing a very long unbroken word, one normal. Observe the overflow. Fix it with min-width: 0 and overflow-wrap: break-word. Explain why the default min-width: auto caused the issue.