Flexbox, properly explained
Flexbox lays things out along one axis. That is the whole idea, and nearly every Flexbox confusion is a case of forgetting which axis you are on.
.row {
display: flex;
gap: 1rem;
}
Three lines and you have a horizontal row with even spacing, no whitespace gap, and equal-height items. That last part is free and used to be very hard.
The two axes
flex-direction: row; /* default: main axis is horizontal */
flex-direction: column; /* main axis is vertical */
flex-direction: row-reverse;
flex-direction: column-reverse;
The main axis is the one flex-direction names. The cross axis is the other
one.
This matters because the alignment properties are named after axes, not directions:
justify-contentaligns along the main axis.align-itemsaligns along the cross axis.
So in a row, justify-content is horizontal and align-items is vertical. Switch
to column and they swap. Every "why is my centring not working" question is
this.
The memory hook: justify-content follows flex-direction. If direction is
row, justify is horizontal. If direction is column, justify is vertical.
Properties on the container
.container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
align-content: flex-start;
gap: 1rem 2rem; /* row-gap column-gap */
}
justify-content
justify-content: flex-start; /* default */
justify-content: flex-end;
justify-content: center;
justify-content: space-between; /* first at the start, last at the end */
justify-content: space-around; /* equal space around each, so half at the edges */
justify-content: space-evenly; /* genuinely equal gaps, including the edges */
space-between is the one you will use for a header: logo on the left, nav on the
right, in two lines of CSS.
The distinction between space-around and space-evenly catches people:
space-around gives each item equal space on both sides, so the gap between two
items is double the gap at the edge. space-evenly makes every gap identical.
align-items
align-items: stretch; /* default — items fill the cross axis */
align-items: center;
align-items: flex-start;
align-items: flex-end;
align-items: baseline; /* align their text baselines */
stretch being the default is why flex items in a row come out the same height
automatically. It is also why a card you expected to hug its content fills the row's
height — set align-items: flex-start if you do not want that.
baseline is the underused one. For a row of a price and a label at different font
sizes, baseline aligns the text rather than the boxes, which is what looks right.
flex-wrap
flex-wrap: nowrap; /* default — items shrink instead of wrapping */
flex-wrap: wrap;
The default is nowrap, and it is the cause of the most common Flexbox
frustration: items squashed into unreadable slivers on a phone rather than wrapping.
flex-wrap: wrap is usually what you meant.
Once items wrap, align-content controls how the lines are distributed on the
cross axis — it does nothing with a single line, which is why it appears not to work.
gap
gap: 1rem;
gap replaced the whole genre of margin-right plus
:last-child { margin-right: 0 }. It applies only between items, never at the
edges, and it does not collapse. Use it.
Properties on the items
.item {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
flex: 0 1 auto; /* the shorthand: grow shrink basis */
align-self: center; /* override align-items for one item */
order: 2;
}
flex, the shorthand worth learning by its three common values
flex: 1; /* = 1 1 0% — share the space equally, ignore content width */
flex: auto; /* = 1 1 auto — grow, but start from the content's width */
flex: none; /* = 0 0 auto — do not grow or shrink */
The difference between flex: 1 and flex: auto is the one to understand, because
it decides whether your columns are equal.
.equal > * { flex: 1; } /* three genuinely equal columns */
.natural > * { flex: auto; } /* wider content gets a wider column */
flex: 1 sets flex-basis: 0%, so every item starts from zero and the free space is
divided equally — equal widths regardless of content. flex: auto starts from the
content width and shares only the leftover, so an item with more text stays wider.
For a sidebar-plus-content layout:
.layout { display: flex; gap: 2rem; }
.sidebar { flex: 0 0 240px; } /* fixed: do not grow, do not shrink, 240px */
.content { flex: 1; } /* takes everything else */
That is the pattern, and flex: 0 0 240px is more reliable than width: 240px,
because a flex item's width can still be shrunk by flex-shrink: 1.
The min-width: 0 problem
The Flexbox bug that costs people the most time:
<div class="row">
<div class="text">A very long unbroken string like a URL or an email address…</div>
<div class="side">Side</div>
</div>
The text refuses to shrink and pushes the row wider than its container. overflow
does nothing. max-width: 100% does nothing.
The cause: a flex item's default min-width is auto, which means "at least as
wide as my content's minimum size" — and for an unbroken string, that is the whole
string. flex-shrink cannot go below that floor.
.text {
min-width: 0;
}
That one declaration fixes it. In a column, the equivalent is min-height: 0.
Combine it with overflow-wrap: break-word and you handle long strings properly.
This is worth committing to memory: if a flex item will not shrink, set
min-width: 0.
order, and why to be careful
.item { order: -1; } /* moves it first */
order changes the visual order only. The DOM order is unchanged, so keyboard
focus still follows the HTML. Reorder things visually and a keyboard user tabs
around the screen in an order that makes no sense, which is a WCAG failure.
Use it for small adjustments at a breakpoint where the reading order still works. Do not use it to restructure a page — fix the HTML instead.
Centring, finally
.centre {
display: flex;
justify-content: center;
align-items: center;
min-height: 100dvh;
}
Horizontally and vertically centred, in four lines. Before Flexbox this was a known hard problem with a dozen partial answers, and it is worth knowing that so you appreciate why people were pleased.
There is also place-items: center, which is shorthand for align-items and
justify-items — but note justify-items has no effect in Flexbox, so
place-items: center in a flex container centres on the cross axis only. It works as
expected in Grid.
Two patterns you will use constantly
A header:
.site-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
A card with a footer pinned to the bottom, regardless of how much text is above:
.card {
display: flex;
flex-direction: column;
}
.card__body { flex: 1; } /* absorbs the spare height */
.card__footer { margin-top: auto; } /* or this, instead */
margin-top: auto on the last item pushes it to the bottom — auto margins eat all
the free space on the main axis. The same trick with margin-left: auto in a row
pushes one item to the right, which is often tidier than restructuring for
space-between.
Check your work
What Flexbox is for. One axis.
justify-content versus align-items. Main axis and cross axis — they swap when
flex-direction changes.
Why flex items come out the same height. align-items: stretch is the default.
space-around versus space-evenly. Equal space around each item, so edges are
half; versus genuinely equal gaps.
The default of flex-wrap. nowrap — items squash rather than wrap.
When align-content does nothing. With a single line.
flex: 1 versus flex: auto. Basis 0%, so equal widths; versus basis auto,
so content affects width.
The reliable way to fix a sidebar's width. flex: 0 0 240px.
Why a flex item will not shrink. Its default min-width: auto is its content's
minimum. Fix with min-width: 0.
Why order is risky. It changes visual order only, so tab order no longer
matches.
What margin-top: auto does in a column. Pushes the item to the bottom by eating
the free space.
Why place-items: center only half works in Flexbox. justify-items has no
effect there.
Practice
- Make a row of three boxes with
display: flexandgap. Note that the HTML whitespace gap is gone. - Centre them with
justify-content: center, then switch toflex-direction: columnand predict what happens before looking. - Try all six
justify-contentvalues. Photographspace-aroundandspace-evenlyside by side. - Put three cards with different amounts of text in a row. Note their equal heights,
then add
align-items: flex-start. - Align a large price and a small label with
align-items: center, thenbaseline. - Put eight items in a row on a 360px viewport with the default
nowrap. Then addwrap. - Add
align-content: centerwith one line, then with three. - Build three columns with
flex: 1, thenflex: auto, with different text lengths. Measure the widths. - Build a 240px sidebar with
width: 240pxand shrink the window until it squashes. Change toflex: 0 0 240px. - Put a 200-character unbroken string in a flex row and watch it break the layout.
Fix it with
min-width: 0. - Use
order: -1on the third item, then tab through the row and note the order. - Centre a box both ways with four lines of CSS.
- Build a card with
margin-top: autoon its footer and vary the body text length. - Push one item to the right of a row with
margin-left: auto. - Turn on the Flexbox overlay in devtools (the
flexbadge next to the element) and watch the axes as you changeflex-direction.
Official documentation
- MDN — Basic concepts of flexbox — The axes, and every container and item property.
- MDN — flex — What
flex: 1,flex: autoandflex: noneexpand to. - CSS-Tricks — A complete guide to Flexbox — Not official, but the diagram everybody keeps open. Use it as a visual index.
Next: Grid, for when one axis is not enough.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship