One axis at a time
display: flex turns an element into a flex container. Its direct children become flex items and line up along the main axis; the perpendicular one is the cross axis.
Everything in flexbox is either "how do items sit along the main axis" or "how do they sit across it". Keep that split in your head and the property names stop being arbitrary.
.row {
display: flex; /* main axis: horizontal */
gap: 16px;
}
.column {
display: flex;
flex-direction: column; /* main axis: vertical */
gap: 16px;
}
Flex applies one level down. A grandchild is laid out by whatever its own parent does.
Direction
row follows the writing direction, so in a right-to-left document it runs the other way on its own. That is the behaviour you want — do not hard-code row-reverse to achieve it.
Reversing changes the visual order only. The tab order still follows the DOM, so a reversed row is a keyboard trap waiting to happen. If the order matters, change the markup.
flex-direction: row; /* default: start to end */
flex-direction: row-reverse;
flex-direction: column;
flex-direction: column-reverse;
Along the main axis: justify-content
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
| Value | Effect |
|---|---|
flex-start | Packed at the start. The default. |
center | Packed in the middle. |
flex-end | Packed at the end. |
space-between | First at the start, last at the end, equal gaps between. |
space-around | Equal space around each item — the outer gaps are half the inner ones. |
space-evenly | Every gap equal, including the outer ones. |
Across it: align-items
align-self overrides it for one item. align-content is the one people confuse: it positions the lines and only does anything when the container wraps.
stretch— the default. Items fill the cross axis, which is why two cards in a row come out the same height for free.center— the vertical centring people used to write hacks for.flex-start/flex-end— packed to one edge.baseline— text baselines line up, whatever the font sizes. Right for a label beside a large number.
Centring, finally
margin: auto on a flex item absorbs all the free space on both axes. It is also how you push one item away from the rest: margin-left: auto on the last item in a row pins it to the right.
.centre {
display: flex;
justify-content: center;
align-items: center;
min-height: 100svh;
}
/* or, for a single child */
.centre-one {
display: flex;
}
.centre-one > * {
margin: auto;
}
Wrapping and gap
gap replaced negative margins entirely. It applies between items only, never at the outer edges, and it works in flex, grid and multi-column.
.deck {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.deck > * {
flex: 1 1 260px; /* grow, shrink, and prefer 260 */
}
flex: the three numbers
flex is shorthand for flex-grow, flex-shrink and flex-basis. Basis is the starting size before any free space is shared out; grow is how much of the leftover space this item takes; shrink is how much it gives up when there is not enough.
flex: 1; /* 1 1 0% — share the space equally, ignore content width */
flex: auto; /* 1 1 auto — share, but start from the content size */
flex: none; /* 0 0 auto — never grow, never shrink */
flex: 0 0 240px; /* a fixed 240px column */
flex: 1 1 260px; /* at least 260 if it fits, otherwise share and wrap */
| Pattern | Use |
|---|---|
flex: 1 on every child | Equal columns whatever the content. |
flex: auto on every child | Columns proportional to their content. |
flex: none on one | A sidebar or an icon that must keep its size. |
flex: 1 on one, flex: none on the rest | One element absorbs the slack — the usual toolbar. |
The overflow trap
A flex item will not shrink below its content's minimum size, because min-width defaults to auto. One long word or a wide table then pushes the whole row out of the container.
.item {
min-width: 0; /* in a row */
}
.column-item {
min-height: 0; /* in a column */
}
A layout that scrolls sideways on a phone, or an ellipsis that never appears. Both are almost always a missing min-width: 0.
order
The DOM order is unchanged, so the reading order and the tab order still follow the source. Use it for small visual adjustments, never to restructure a page.
.promoted { order: -1; } /* moves it first visually */