Flexbox is a layout model for one dimension at a time: a row or a column, with the items inside sized and positioned along it. It is the tool for toolbars, navigation bars, card rows, form layouts and anything else where items sit beside each other and need to share the space evenly.
What are the two roles?
Turning an element into a flex container makes its children flex items. Everything else on the page is unaffected, and the children become blocks that lay out along an axis instead of stacking.
<div class="toolbar">
<button>Save</button>
<button>Cancel</button>
<span class="spacer"></span>
<button>Help</button>
</div>
.toolbar {
display: flex;
gap: 0.5rem;
}
The three buttons now sit in a row, side by side, with half a rem between them. Before the flex declaration they would have stacked vertically, each on its own line, because a button is an inline-block element and the whitespace between them was all that kept them apart.
Two properties matter immediately. flex-direction sets the main axis, and it defaults to a row. And gap sets the space between the items, which is clearer than margins because it does not add space at the ends of the container and cannot collapse.
What are the two axes?
Every flexbox property is measured against two axes. The main axis is the direction the items flow — horizontally for a row, vertically for a column. The cross axis is the other one. The alignment properties are named for the axis they affect, and this is the single most useful thing to remember.
.row {
display: flex;
flex-direction: row; /* main axis: horizontal */
justify-content: flex-start; /* along the main axis */
align-items: stretch; /* along the cross axis */
}
.column {
display: flex;
flex-direction: column; /* main axis: vertical */
justify-content: flex-start; /* now: top to bottom */
align-items: stretch; /* now: left to right */
}
That swap is the moment flexbox clicks for most people. In a column, justify-content moves things up and down, and align-items moves them left and right — the opposite of what the names suggest until you know which axis is which.
What happens to the items by default?
Three defaults do most of the work, and understanding them explains most flexbox behaviour without reading the specification.
/* the defaults, written out */
.item {
flex-grow: 0; /* do not grow */
flex-shrink: 1; /* do shrink if needed */
flex-basis: auto; /* start from the content size */
}
So by default, items are sized by their content, they do not grow to fill the container, and they do shrink if the container is too small. A row of three buttons therefore sits at the left, at its natural width, with the space to the right empty.
.toolbar {
display: flex;
gap: 0.5rem;
}
Two more defaults shape the cross axis. align-items: stretch makes items fill the container's height, which is why buttons in a row all end up the same height. And the items are aligned to the start of the cross axis, so a short item next to a tall one sits at the top.
What happens when the items do not fit?
By default flex items shrink rather than wrap, and because their minimum size is their content size, a row of long items can overflow the container. Two properties change this: allowing the items to wrap, and giving them a smaller minimum size.
.tags {
display: flex;
flex-wrap: wrap; /* move items to a new line when they run out of room */
gap: 0.5rem;
}
.tag {
min-width: 0; /* allow an item to shrink below its content width */
}
The min-width: 0 is the detail that catches people. By default a flex item has an automatic minimum size equal to its content, so it refuses to shrink below the width of its longest word. Setting it to zero lets the item shrink, and the content then needs overflow: hidden or truncation to deal with what no longer fits.
When should you reach for flexbox?
Flexbox is for one dimension. A row of cards that should wrap, a navbar that pushes one item to the far end, a form with a label and a field side by side, a vertically centred hero — all of these are one-dimensional problems, and flexbox solves them in a line or two of CSS.
When you need to control rows and columns together — a page shell with a header, a sidebar and a content area, or a card grid with cards of different heights — grid is the right tool, because it handles two dimensions at once. The two models also nest: a grid page with flexbox inside each cell is a completely normal and effective combination.
one dimension -> flexbox
a row of items
a vertical stack
centring in one axis
two dimensions -> grid
rows and columns
a page shell
a card grid
What do align-content and gap actually do?
Two flex container properties are easy to overlook, and both of them matter as soon as your row wraps.
gap is the spacing between items. It replaces the margin hack that used to be necessary - a margin on every item except the last one - and it applies in both directions without you having to think about which edge you are on. It is also animatable, which margins are not, and it does not add space at the end of the row.
align-content controls what happens when there is more than one line. It only does anything when the container wraps and the items do not fill the cross axis, and it distributes the lines themselves. align-content: space-between spreads the rows apart, align-content: center groups them in the middle, and the default of flex-start leaves them packed at the top.
<div class="cards">
<article class="card">One</article>
<article class="card">Two</article>
<article class="card">Three</article>
<article class="card">Four</article>
<article class="card">Five</article>
</div>.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
/* the cards stretch to fill the row */
flex: 1 1 12rem;
/* and the leftover space is shared out between the lines */
align-content: space-between;
}That combination - a wrapping row, a flex basis that sets the minimum useful width, and a gap - is the whole recipe for a responsive card row that needs no media query. The cards wrap into as many lines as the width allows, each line shares the leftover space evenly, and the spacing between them is always the same.
One caution about gap in older code: it used to be unsupported in flexbox and needed a margin hack, which is why you still see that pattern in stylesheets written years ago. If you maintain one of those, the gap version is shorter and does not break the first and last items.
Quick reference
display: flexon the container makes its children flex items.- The main axis is the direction of the flow; the cross axis is the other one.
justify-contentaligns along the main axis;align-itemsalong the cross axis.- In a column, the two axes swap roles — this is the confusing part.
- Items do not grow by default;
flex: 1makes them share the space. min-width: 0lets an item shrink below its content size.
Try it yourself: take a toolbar with three buttons, make it a flex row, and use the developer tools to see the flex overlay. Then change the direction to a column and watch the alignment properties swap jobs. Finally, add flex-wrap: wrap and shrink the window to see the items move to a new line.