Flexbox

Lay things out along one axis and control how the free space is shared.

09of 26
≈ 14Min read
9Parts

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.

the whole idea
.row {
  display: flex;          /* main axis: horizontal */
  gap: 16px;
}

.column {
  display: flex;
  flex-direction: column; /* main axis: vertical */
  gap: 16px;
}
Only direct children

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.

four values
flex-direction: row;             /* default: start to end */
flex-direction: row-reverse;
flex-direction: column;
flex-direction: column-reverse;

Along the main axis: justify-content

the classic header
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
ValueEffect
flex-startPacked at the start. The default.
centerPacked in the middle.
flex-endPacked at the end.
space-betweenFirst at the start, last at the end, equal gaps between.
space-aroundEqual space around each item — the outer gaps are half the inner ones.
space-evenlyEvery 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.

both axes, two lines
.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.

a card deck that wraps
.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.

the values you will actually write
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 */
PatternUse
flex: 1 on every childEqual columns whatever the content.
flex: auto on every childColumns proportional to their content.
flex: none on oneA sidebar or an icon that must keep its size.
flex: 1 on one, flex: none on the restOne 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.

the fix you will need over and over
.item {
  min-width: 0;        /* in a row */
}

.column-item {
  min-height: 0;       /* in a column */
}
The symptom

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.

visual order only
.promoted { order: -1; }   /* moves it first visually */

Practice