Module 5: Layout & Positioning
Flexbox: One-Dimensional Layout and How the Algorithm Shares Space
Main and cross axes, every container and item property, the exact math behind flex-grow, flex-shrink and flex-basis, and the min-width trap.
Flexbox lays items out along one line at a time, either a row or a column, and distributes the space between them. It exists to solve what floats and inline-blocks never did well: aligning things, centering them, and letting them grow and shrink to fill a container.
You turn it on with display: flex on the parent. The parent is the flex container, and its direct children become flex items. Grandchildren are not affected.
.toolbar {
display: flex;
}
Two axes#
Everything in flexbox is described relative to two axes.
flex-direction: row (default)
main axis ──────────────────────────────►
┌────────────────────────────────────────────┐ ▲
│ ┌──────┐ ┌──────────┐ ┌────┐ │ │
│ │ item │ │ item │ │item│ │ │ cross axis
│ └──────┘ └──────────┘ └────┘ │ │
└────────────────────────────────────────────┘ ▼
flex-direction: column → the axes swap
- The main axis is the direction items are laid out, set by
flex-direction. - The cross axis is perpendicular to it.
This swap is the source of the most common flexbox confusion. justify-content always works along the main axis, and align-items always works along the cross axis. When you change the direction to column, they swap which physical direction they control.
Container properties#
.container {
display: flex;
flex-direction: row; /* row | row-reverse | column | column-reverse */
flex-wrap: nowrap; /* nowrap | wrap | wrap-reverse */
justify-content: flex-start; /* distribute along the main axis */
align-items: stretch; /* align along the cross axis */
align-content: stretch; /* spacing between wrapped lines */
gap: 1rem; /* space between items */
}
justify-content distributes leftover space along the main axis:
flex-start [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]···
align-items aligns items on the cross axis: stretch (the default, items grow to the container's height), flex-start, flex-end, center and baseline (aligns the text baselines).
align-content only has an effect when flex-wrap produces several lines. It spaces the lines, while align-items aligns items within each line.
gap is the modern way to space items. Unlike margins, it adds space only between items, never on the outer edges.
Item properties#
.item {
flex-grow: 0; /* share of positive free space */
flex-shrink: 1; /* share of negative free space */
flex-basis: auto; /* starting size before growing or shrinking */
align-self: auto; /* override align-items for this item */
order: 0; /* visual order */
}
The first three are normally written as the flex shorthand: flex: grow shrink basis.
How the algorithm actually works#
Understanding this removes the guesswork. The browser does the following on the main axis:
- Each item gets a starting size, its flex-basis. If it is
auto, the browser uses the item'swidth(orheightin a column), or its content size if none is set. - It adds up all those sizes plus gaps and compares the total with the container.
- If there is extra space, it shares it out according to
flex-growratios. - If there is too little space, it removes it according to
flex-shrinkratios, weighted by each item's basis. Bigger items shrink more. - Min and max constraints are applied, and anything that violates them is frozen and the rest recalculated.
A worked example. A 600px container, three items, each with flex-basis: 300px:
.item { flex: 0 1 300px; }
Total basis: 900px Container: 600px Overflow: 300px
Shrink is equal and so is the basis, so each item gives up 100px.
Result: 200px, 200px, 200px
And with extra space. A 600px container, items with basis 100px, and flex-grow of 1, 1 and 2:
Total basis: 300px Free space: 300px Grow total: 4
Item 1: 100 + 300 × 1/4 = 175px
Item 2: 100 + 300 × 1/4 = 175px
Item 3: 100 + 300 × 2/4 = 250px
The shorthands you will actually use#
| Shorthand | Expands to | Meaning |
|---|---|---|
flex: 1 | 1 1 0% | share space equally, ignoring content size |
flex: auto | 1 1 auto | grow and shrink from the content size |
flex: none | 0 0 auto | rigid, never resizes |
flex: 0 0 200px | fixed 200px column |
flex: 1 sets the basis to zero, so each item starts from nothing and all free space is divided. Items with flex: 1 therefore come out equal. Compare that with flex: auto, where a longer item starts bigger and stays bigger.
The min-width: auto trap#
Flex items have an automatic minimum size: they will not shrink below the size of their content. A long unbreakable string, a wide image or a pre block forces the item to stay large, and the container overflows.
.layout { display: flex; }
.sidebar { flex: 0 0 240px; }
.main { flex: 1; } /* long code or a wide table here pushes the layout out */
The fix is to remove that minimum:
.main {
flex: 1;
min-width: 0; /* now it may shrink below its content; overflow handling takes over */
}
Combined with overflow: auto on the inner content, long lines scroll instead of breaking the page. The same trap applies vertically in column layouts with min-height: 0. It is also the reason text-overflow: ellipsis does not work on a flex item until you set min-width: 0.
Patterns you will build constantly#
Center anything:
.center {
display: flex;
justify-content: center;
align-items: center;
min-height: 100dvh;
}
Navbar with the logo on the left and links on the right:
.nav { display: flex; align-items: center; gap: 1rem; }
.nav .logo { margin-right: auto; } /* auto margin eats all free space */
Auto margins in flex absorb free space before justify-content does, which makes them a good way to push one item away from the others.
Equal-height cards with the button pinned to the bottom:
.cards { display: flex; gap: 1rem; }
.card { flex: 1; display: flex; flex-direction: column; }
.card .actions { margin-top: auto; }
align-items: stretch already makes the cards equal height. The nested column flex plus margin-top: auto pins the actions to the bottom, whatever the text length.
Sticky footer:
body { min-height: 100dvh; display: flex; flex-direction: column; }
main { flex: 1; }
Wrapping tag list:
.tags { display: flex; flex-wrap: wrap; gap: 0.5rem; }
When flexbox is the wrong tool#
Flexbox is one-dimensional. When you wrap items onto several lines, each line is laid out independently, so columns do not line up from row to row. If you want a true grid where rows and columns both align, use CSS Grid (next lesson). A rough rule: use flexbox when the content decides the layout, and grid when you decide the layout first and fit the content in.
Common mistakes#
- Confusing
justify-contentandalign-itemsafter changingflex-direction. - Applying flex properties to the wrong element.
justify-contentgoes on the container,flex: 1goes on the item. - Expecting grandchildren to be flex items. Only direct children are.
- Forgetting
min-width: 0, and wondering why long content breaks the layout. - Using
widthandflex: 1together and getting surprising results. Underflex: 1the width is ignored because the basis is0%. - Using margins for spacing between items and ending up with an extra margin on the last item. Use
gap. - Using flexbox for a two-dimensional grid and fighting alignment.
- Using
orderto reorder content visually. Keyboard focus and screen readers still follow the HTML order, which then does not match what users see.
Practice#
- Build a horizontal nav with the logo on the left and three links and a button on the right, using
margin-right: auto. - Build three equal cards with long and short text, a heading and a button. Make all cards equal height and pin the buttons to the bottom edge.
- Make a 600px container with three items of
flex: 0 1 300px. Predict each item's width, then measure it. Change one item'sflex-shrinkto 2 and recalculate. - Put a long
preblock inside.mainof a flex layout and break it. Then fix it withmin-width: 0andoverflow: auto.