Frontend Development · Tutorial 4 of 7
CSS Flexbox in Depth: alignment, wrapping and ordering
By the FUTEAC Team · 13 November 2026 · 11 min read · Intermediate
In short
Flexbox gives you three controls over a row or column of items: alignment (where items sit on each axis), wrapping (what happens when they run out of room), and ordering (which item appears first). Set display: flex on the container, then use justify-content and align-items for position, flex-wrap for overflow, and order for sequence.
- Two axes, two properties.
justify-contentworks on the main axis,align-itemson the cross axis. Mixing them up is the most common Flexbox confusion. - Wrapping is off by default. Without
flex-wrap: wrap, items shrink to fit one row instead of dropping to the next line. flex-basismeasures the content box unless you setbox-sizing: border-box. Padding is added on top by default, and that is why three cards can become two.orderchanges only what you see. The HTML order, tab order and screen reader order stay exactly as written.- This tutorial feeds the HTML & CSS Foundations course (/course/html-css-foundations) and the CSS Flexbox (/skill/css-flexbox) skill.
Before you start
Which way is which: the main axis and the cross axis
Every flex container has two axes. The main axis runs in the direction items are laid out. The cross axis runs across it, at right angles. flex-direction picks which way the main axis points: row (the default) lays items out left to right, so the main axis is horizontal; column stacks them top to bottom, so the main axis is vertical.
Think of books on a shelf. In a row, you slide books left and right along the shelf (main axis) and line up their tops or bottoms (cross axis). Turn the shelf on its side and the two directions swap jobs. Flexbox works the same way: when you switch flex-direction to column, justify-content starts spacing things vertically and align-items starts lining them up horizontally.
That swap is the single fact behind most Flexbox confusion. If a property seems to do nothing, or does the opposite of what you expected, check which direction your main axis points first.
.row { display: flex; flex-direction: row; } /* main axis: horizontal */
.stack { display: flex; flex-direction: column; } /* main axis: vertical */How do you spread items along the main axis?
justify-content decides how items share the space along the main axis. There are six values worth knowing. flex-start packs items at the start, flex-end packs them at the end, center groups them in the middle. The three space- values spread them out: space-between pushes the first item flush to the start edge and the last flush to the end edge, sharing all the leftover space between items; space-around gives each item an equal cushion on both sides, so the gaps at the edges are half the gaps between items; space-evenly makes every gap equal, edges included.
Here is space-between with real numbers. A 600px-wide container, 120px tall, holding three 100px-wide items, the middle one taller than the others:
<div class="row">
<div class="item">One</div>
<div class="item tall">Two</div>
<div class="item">Three</div>
</div>.row {
display: flex;
justify-content: space-between;
align-items: center;
height: 120px;
width: 600px;
border: 2px solid #0f766e;
}
.item { width: 100px; height: 60px; background: #f5efe3; }
.tall { height: 82px; }Rendered in Chromium 141, the first item sits flush to the left inner edge and the last item sits flush to the right: its right edge measures 618px against the container's inner right edge of 620px, the 2px difference being the container border. The gap between items is shared equally, which is exactly what space-between promises. This is the value you want for a navbar with a logo on the left and a button on the right, or a row of cards spread across the full width.
How do you line items up on the cross axis?
align-items decides how items sit on the cross axis, the direction across the row. The default is stretch: items with no set height stretch to fill the container, which surprises beginners who expected their cards to keep their own height. flex-start lines up the top edges, flex-end the bottom edges, center centres each item, and baseline lines up the text baselines, handy when items have different font sizes.
Look at the same example again. The middle item is 82px tall in a 120px container, and its top measures 19px below the container top: (120 - 82) / 2 = 19. It is exactly centred. The two 60px items have their tops 30px down: (120 - 60) / 2 = 30. The point to take away: align-items: center centres each item by its own height, so items of different heights do not share a top edge. If you want their tops to line up, use align-items: flex-start instead.
One more property belongs here. When a flex container wraps into several rows, align-content controls how those rows spread across the container, the same way justify-content spreads items along a row. You only need it on multi-line containers, but when a wrapped card grid has a strange vertical gap, align-content is usually the missing piece.
What happens when the items run out of room?
By default, flex items never wrap. flex-wrap is nowrap, so when five cards do not fit in one row, the browser squeezes them narrower instead of dropping one to the next line. Text overflows, images squash, and the layout looks broken in a way that is hard to name. Add flex-wrap: wrap and items that do not fit move to a new row, like words wrapping in a paragraph.
.cards {
display: flex;
flex-wrap: wrap;
gap: 12px;
width: 600px;
}
.card { flex: 1 1 180px; padding: 12px; }gap sets the space between items, 12px here, in both directions at once. We use gap, not margins, for the space between flex items, because gap only appears between items, never on the outer edges, and it disappears cleanly when items wrap. A margin on every card would put 12px on the outside of the first and last cards too, and double up to 24px where two cards meet.
The wrong turn: why did my three cards become two?
This is the mistake that teaches the box model properly. Take the card row above: a 600px container, five cards, each flex: 1 1 180px with 12px of padding on each side. Count it up: 3 × 180 + 2 × 12 of gap = 564px. That fits in 600px, so three cards per row, surely.
It does not fit. Rendered, only two cards sit on row 1, each stretched to 294px wide: (600 - 12) / 2. Card 3 wraps to row 2, card 4 joins it, and the last card sits alone on row 3, stretched to the full 600px by flex-grow. The layout is not broken. Your arithmetic was wrong, because flex-basis is measured on the content box by default and the padding is added on top. Each card's real outer width is 180 + 12 + 12 = 204px, so three cards need 3 × 204 + 2 × 12 = 636px. 636 is more than 600, so the third card wraps.
The fix is the lesson from item 13, the CSS Box Model tutorial: put box-sizing: border-box on the cards, and the basis becomes the full width, padding included.
.card {
box-sizing: border-box;
flex: 1 1 180px;
padding: 12px;
}With that one line, three cards fit per row at 192px each: (600 - 24) / 3, and the remaining two sit on row 2 at 294px each. Same HTML, same flex values, different box model. If a flex row ever wraps one item too early, check box-sizing before you touch anything else.
What do flex-grow, flex-shrink and flex-basis actually do?
flex: 1 1 180px is shorthand for three values: flex-grow: 1, flex-shrink: 1, flex-basis: 180px. flex-basis is the item's starting size, before any growing or shrinking. flex-grow says how the leftover space is shared when items are narrower than the container. flex-shrink says how a shortfall is shared when they are wider.
In the fixed card row, three cards with a 180px basis and 24px of gaps take 564px of a 600px container. There are 36px left over. Each card has flex-grow: 1, so the 36px is shared equally, 12px each, and each card ends at 192px. Give the middle card flex-grow: 2 and it would take twice the leftover share of its neighbours. Set flex: 1 on its own and you get 1 1 0%: a zero basis, so all the space is shared out by grow alone. Set flex: none and you get 0 0 auto: the item keeps its natural size and never grows or shrinks.
The mental model: basis is the size you ask for, grow is how you share the spare room, shrink is how you share the squeeze. Most of the time flex: 1 1 180px on cards and flex: 1 on content areas covers everything a beginner layout needs.
How do you move one item without touching the HTML?
order changes the visual order of flex items. Every item starts at order: 0. Give one item a lower number and it moves earlier; a higher number moves it later. Three items in HTML order First, Second, Third, with the third given order: -1:
<div class="row">
<div class="item">First</div>
<div class="item">Second</div>
<div class="item last">Third</div>
</div>.row { display: flex; }
.last { order: -1; }Rendered, the visual order left to right is Third, First, Second. But order changes only what you see. The HTML order is unchanged, so keyboard tab order and screen readers still follow First, Second, Third. Never use order to fix a wrong HTML order. Fix the HTML, because a sighted mouse user and a keyboard user would otherwise meet two different pages. The honest uses are visual: pulling a "Sale" badge to the front of a card, or moving a button first on a narrow screen while the HTML stays in reading order.
Putting it together: a card row that behaves
Here is the full card row from this tutorial, with every fix applied. Five cards, a 600px container, wrapping on, border-box set, gap for spacing:
<div class="cards">
<div class="card">Card 1</div>
<div class="card">Card 2</div>
<div class="card">Card 3</div>
<div class="card">Card 4</div>
<div class="card">Card 5</div>
</div>.cards {
display: flex;
flex-wrap: wrap;
gap: 12px;
width: 600px;
}
.card {
box-sizing: border-box;
flex: 1 1 180px;
padding: 12px;
}Expected result, as rendered: three cards on row 1 at 192px each, two cards on row 2 at 294px each. Shrink the container to 380px and the same CSS gives two per row, no media query needed yet. That is the whole tutorial in one example: alignment from the container, wrapping from flex-wrap, sizing from flex-basis plus border-box, spacing from gap.
Where to go next
- Pillar: Learn Frontend Development: The Complete Beginner Guide (/tutorials/frontend)
- Course: HTML & CSS Foundations (/course/html-css-foundations), lesson "Flexbox Layouts in Practice" in the module "Layout and Responsive Design"
- Roadmap: Frontend Developer (/roadmap/frontend-developer), Weeks 1–3, "CSS styling, Flexbox and Grid", project "A personal portfolio website"
- Skills: CSS Flexbox (/skill/css-flexbox) (5h), CSS (/skill/css), Responsive Design (/skill/responsive-design)
- Previous in this series: CSS Flexbox vs Grid: which one, when (/tutorials/frontend/flexbox-vs-grid) (item 19)
- Next in this series: CSS Grid in Depth: template areas, auto-fit and minmax (/tutorials/frontend/css-grid-in-depth) (item 48, delivered with this pair)
- Also in this series: Responsive Design with Media Queries: a worked example (/tutorials/frontend/responsive-design-media-queries) (item 25)
Try it yourself
Build a small site header with Flexbox. Logo on the left, three navigation links in the middle, one button on the right. Use display: flex, justify-content: space-between and align-items: center on the header, and gap between the links. Then shrink the header to 380px wide and watch what happens. Add flex-wrap: wrap and see the links drop to a second row instead of squeezing. Finally, give the button order: -1 and check that it jumps to the front visually, then remove the order rule again, because the HTML order was right all along.
Common mistakes
- Forgetting
flex-wrap. Items shrink to fit one row instead of wrapping, and text overflows. If your row looks squashed, checkflex-wrapfirst. - Using
justify-contentto centre things vertically in a row. In a row,justify-contentworks horizontally. Vertical centring isalign-items: center. If the property seems to do nothing, your axes are swapped in your head. - Spacing with margins instead of
gap. Margins put space on the outer edges too and double up where items meet.gaponly puts space between items. - Using
orderto fix HTML order. The visual order changes but tab order and screen readers still follow the HTML. Fix the markup instead. - Forgetting
box-sizing: border-box. Padding is added on top offlex-basisby default, so three 180px cards with 12px padding need 636px, not 564px, and the third card wraps early.
Frequently asked questions
When should I use Flexbox instead of Grid?
When the layout runs in one direction: a row of cards, a navbar, a line of buttons. Grid is for layouts that need rows and columns at the same time, like a whole page. Item 19, CSS Flexbox vs Grid: which one, when, works through the decision in detail.
Why are my flex items different heights?
Because align-items defaults to stretch, items with no set height stretch to the container height, while items with a set height keep it. Set align-items: flex-start if you want every item to keep its own height and line up at the top.
Why does justify-content: center not centre my items vertically?
In a row, justify-content works on the horizontal main axis. For vertical centring in a row, use align-items: center. If you want justify-content to work vertically, switch the container to flex-direction: column.
Does order change tab order?
No. Tab order and screen reader order follow the HTML, never the visual order. That is why order is for visual tweaks only, never for fixing markup that is in the wrong sequence.
Should I use flex-basis or width on a flex item?
Inside a flex container, flex-basis is the size the browser uses. width only matters when flex-basis is auto. Set the basis and let grow and shrink do the rest.
Follow the Frontend Developer Roadmap
Build modern, responsive websites and web apps with HTML, CSS, JavaScript and React. Free, step by step, at your pace.
View the Roadmap