Flexbox
By the end of this lesson
Lay out and align items along a single axis.
Flexbox arranges a set of items along one line: a row or a column. It handles the spacing between them, the alignment across them, and how leftover or missing space is shared out.
That one-line focus is the thing to hold onto. Most of the confusion around flexbox comes from trying to use it for a grid of rows and columns, which is the next lesson's job.
Four words that make the rest of the properties readable:
- Flex container
- The element you put display: flex on. It controls the arrangement of its direct children.
- Flex item
- Each direct child of that container. Grandchildren are not flex items — they are laid out normally inside their own parent.
- Main axis
- The direction items flow in. Horizontal by default; vertical when you set flex-direction: column. Properties named justify-* work along this axis.
- Cross axis
- The perpendicular direction. Properties named align-* work along this one. Swap the direction and the two axes swap with it.
.directory-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}- display: flex turns the toolbar into a flex container. Its children now sit in a row instead of stacking.
- justify-content: space-between spreads the children along the main axis, pushing the first to the start and the last to the end. It only has an effect when there is spare space.
- align-items: center centres them on the cross axis, so a tall button and a short label line up through their middles. This is the property that makes vertical centring a single line.
- gap: 12px is the minimum space between items. It applies between them only, never before the first or after the last, which is what makes it easier than margins.
- flex-wrap: wrap lets items move onto a second line when they no longer fit. Without it they shrink instead, and eventually overflow. On a narrow screen that one word often removes the need for a media query.
.expense-row {
display: flex;
align-items: baseline;
gap: 16px;
}
.expense-description {
flex: 1 1 auto;
min-width: 0;
}
.expense-category {
flex: 0 0 120px;
}
.expense-amount {
flex: 0 0 auto;
font-variant-numeric: tabular-nums;
}- align-items: baseline lines the text up along the bottom of the letters rather than the box edges, which looks steadier when font sizes differ within the row.
- flex is shorthand for three values: grow, shrink, then basis. flex: 1 1 auto means take a share of any spare space, give space back when short, and start from the content's natural size.
- flex: 0 0 120px on the category means do not grow, do not shrink, stay 120px. A fixed column inside a flexible row.
- min-width: 0 on the description looks strange and matters. A flex item will not shrink below the width of its content by default, so a long description pushes the row wider than its container. Setting it to 0 allows the item to shrink so text can wrap or be truncated.
- tabular-nums makes every digit the same width, so a column of amounts lines up instead of drifting.
Flexbox is the right tool when:
- Items sit in a single row or column — a toolbar, a row of buttons, a card footer
- Their sizes come from their content rather than from a layout you decided in advance
- You want one item to absorb the leftover space and the rest to keep their size
- You need vertical centring, which align-items: center gives you in one line
- The number of items varies and they should wrap when space runs out
Summary
- Flexbox lays items out along one axis and shares out the leftover space
- justify-* works along the main axis, align-* across it, and flex-direction decides which is which
- flex is grow, shrink and basis — decide which item absorbs spare space
- A flex item will not shrink below its content unless you allow it with min-width: 0
- Use gap rather than margins, and reorder markup rather than only the visual order
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Try it yourself
Build the expense row
Lay out a row with four parts: a date on the left at a fixed width, a description that takes all remaining space, a category tag that keeps its size, and an amount aligned right.
Then make it survive a description of sixty words and a viewport of 320 pixels.
Show solution
Only the description gets flex-grow, so it is the only part that changes size when the row does. The fixed parts use flex: 0 0 with a basis, which is clearer than setting width on a flex item.
min-width: 0 on the description is what survives the sixty-word test. Without it the item refuses to shrink below its content and the row overflows, which reads as a mysterious horizontal scrollbar.
Wrapping at 320px is a judgement call. Allowing the row to wrap keeps everything readable; keeping it on one line keeps the columns aligned. For a scannable list, alignment usually matters more, so truncation with a title attribute for the full text is a reasonable trade.
.expense-row {
display: flex;
align-items: center;
gap: 16px;
}
.expense-date {
flex: 0 0 6rem;
color: #52606d;
}
.expense-description {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.expense-category {
flex: 0 0 auto;
}
.expense-amount {
flex: 0 0 5rem;
text-align: right;
font-variant-numeric: tabular-nums;
}Think about it
Think about it
You need the last item in a flex row pushed to the far end, and the others grouped at the start. You could use justify-content: space-between, or margin-left: auto on the last item. When does each one break?
Show solution
space-between distributes every gap, so with three or more items the middle ones spread out too. It is right when you want even distribution, wrong when you want "a group, then one thing at the end".
An auto margin absorbs all the spare space at that one point, which gives exactly that grouping. It breaks when the row wraps, because the item is then alone on its line with the margin still pushing it over.
A third option is often best: wrap the grouped items in their own container, then space-between applies to two children and the intent is visible in the markup.
Saved in this browser only.