Skip to content

Flexbox

Flexbox lays out children in one dimension — a row or a column — and distributes free space among them. It is the right tool for toolbars, navbars, button groups, card footers, centring, and anything where “these items sit in a line and share the space”.

For two-dimensional layout, where rows and columns must align, use Grid.

.container { display: flex; }

That single declaration does three things: the container becomes a block-level box, its direct children become flex items, and those items lay out along the main axis.

  • Main axis — the direction items flow. Set by flex-direction. Horizontal by default.
  • Cross axis — perpendicular to the main axis.
  • Main start / main end, cross start / cross end — the edges. These flip with flex-direction and with writing direction, which is why the properties are named justify-* (main) and align-* (cross) rather than “horizontal” and “vertical”.

Everything else follows from that: justify-content distributes along the main axis, align-items along the cross axis. Change flex-direction and the two swap meaning.

Only direct children become flex items. Text nodes directly inside the container become anonymous flex items too.

.c { flex-direction: row; } /* default: left→right (in LTR) */
.c { flex-direction: row-reverse; } /* right→left */
.c { flex-direction: column; } /* top→bottom */
.c { flex-direction: column-reverse; } /* bottom→top */

row follows the writing direction, so in an RTL document row runs right to left automatically.

.c { flex-wrap: nowrap; } /* default: items shrink rather than wrap */
.c { flex-wrap: wrap; }
.c { flex-wrap: wrap-reverse; } /* new lines stack towards cross-start */
.c { flex-flow: row wrap; } /* shorthand: direction + wrap */

nowrap is the default and is why a row of items squashes instead of moving to a second line.

.c { justify-content: flex-start; } /* default */
.c { justify-content: flex-end; }
.c { justify-content: center; }
.c { justify-content: space-between; } /* first at start, last at end, equal gaps between */
.c { justify-content: space-around; } /* equal space around each item: edges get half */
.c { justify-content: space-evenly; } /* every gap equal, including the edges */

justify-content only has an effect when there is free space on the main axis. If items fill the line (or overflow it), nothing moves.

.c { align-items: stretch; } /* default: items fill the cross size */
.c { align-items: flex-start; }
.c { align-items: flex-end; }
.c { align-items: center; }
.c { align-items: baseline; } /* align the items' first text baselines */

stretch is why flex items in a row all come out the same height by default — they stretch to the tallest one. It only applies if the item’s cross size is auto (no explicit height in a row layout).

baseline is the right choice when items contain text at different sizes and you want the text to line up rather than the boxes.

align-content — cross axis, between lines

Section titled “align-content — cross axis, between lines”

Only meaningful when the container wraps into multiple lines and has extra cross-axis space:

.c {
flex-wrap: wrap;
height: 400px;
align-content: space-between; /* also: flex-start|flex-end|center|space-around|stretch */
}
.c { gap: 1rem; } /* both axes */
.c { gap: 1rem 2rem; } /* row-gap column-gap */
.c { column-gap: 2rem; }

gap in flexbox is supported in every current browser (Chrome 84+, Firefox 63+, Safari 14.1+). It replaces the old margin + negative-margin-on-parent hack entirely, and unlike margins it does not add space outside the first and last items.

The place-content / place-items shorthands work here too:

.c { place-items: center; } /* align-items + justify-items (justify-items is ignored in flex) */
.c { place-content: center; } /* align-content + justify-content */

Note justify-items has no effect in flexbox — there is no per-item main-axis alignment property on the container. Use margin: auto on the item instead.

How much of the positive free space this item absorbs, as a proportion of all items’ grow factors.

.a { flex-grow: 1; }
.b { flex-grow: 2; } /* takes twice as much of the leftover space as .a */

flex-grow: 2 does not make an item twice as wide as its sibling. It takes twice as much of the remaining space after every item’s base size has been laid out. If both items start at 100px and there is 300px free, .a ends at 200px and .b at 300px.

Default: 0 (do not grow).

How much of the negative free space (overflow) this item gives up. Default: 1, which is why flex items shrink below their width by default.

.logo { flex-shrink: 0; } /* never shrink this */

Shrinking is weighted by the item’s base size as well as its shrink factor, so a wide item shrinks more than a narrow one at the same factor.

The item’s size along the main axis before growing or shrinking.

.item { flex-basis: 200px; }
.item { flex-basis: 30%; } /* percentage of the container's main size */
.item { flex-basis: auto; } /* default: use width/height, else content size */
.item { flex-basis: content; }/* size from content, ignoring width */

When flex-basis is anything other than auto, it overrides width (in a row) or height (in a column).

Always use the shorthand. It sets sensible defaults for the parts you leave out, and the longhand defaults are different from the shorthand defaults.

.item { flex: 1; } /* = 1 1 0% grow, shrink, ignore content size */
.item { flex: auto; } /* = 1 1 auto grow, shrink, start from content size */
.item { flex: none; } /* = 0 0 auto fixed at content size */
.item { flex: initial; } /* = 0 1 auto the default: shrink only */
.item { flex: 0 0 240px; } /* fixed 240px track */
.item { flex: 2 1 300px; } /* grow twice as fast, shrink normally, base 300px */

The difference between flex: 1 and flex: auto is the single most useful thing to internalise:

  • flex: 1 → basis 0. Content size is ignored; all items end up equal width regardless of content.
  • flex: auto → basis auto. Content size is the starting point; items with more content end up wider.
<div class="row">
<div class="col">Short</div>
<div class="col">Much much longer content here</div>
</div>
.row { display: flex; gap: 1rem; }
.col { flex: 1; } /* two equal halves */
/* .col { flex: auto; } -> the second column is wider */

Overrides align-items for one item.

.container { align-items: center; }
.container .last { align-self: flex-end; }

Changes the visual order. Default 0; items sort by order, then by source order. Negative values are allowed.

.sidebar { order: -1; } /* move it first without changing the HTML */

An auto margin absorbs all free space on that side, before justify-content gets a look in. This is the flexbox way to push one item away from the rest:

.nav { display: flex; gap: 1rem; }
.nav .login { margin-inline-start: auto; } /* pinned to the far end */
.center {
display: flex;
justify-content: center; /* main axis */
align-items: center; /* cross axis */
min-height: 100dvh;
}
<nav class="nav">
<a class="brand" href="/">Acme</a>
<ul class="links">
<li><a href="/docs">Docs</a></li>
<li><a href="/pricing">Pricing</a></li>
</ul>
</nav>
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.75rem 1.5rem;
}
.links { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }
.layout { display: flex; gap: 2rem; }
.sidebar { flex: 0 0 260px; } /* fixed, never grows or shrinks */
.main { flex: 1; min-width: 0; } /* takes the rest — see the min-width note below */
body { min-height: 100dvh; display: flex; flex-direction: column; }
main { flex: 1; } /* main absorbs all leftover height, pushing the footer down */
.media { display: flex; gap: 1rem; align-items: flex-start; }
.media img { flex: none; inline-size: 64px; block-size: 64px; border-radius: 50%; }
.media .body { flex: 1; min-width: 0; }
.cards { display: flex; flex-wrap: wrap; gap: 1rem; }
.card { flex: 1 1 280px; } /* at least 280px, then share leftover space */

This gives a responsive grid with no media queries: cards sit side by side until they cannot be 280px wide, then wrap.

Flex items have an automatic minimum size: they will not shrink smaller than their content. That is why a long unbreakable string or a nested overflowing element blows out the layout, and why overflow: hidden / text-overflow: ellipsis on a flex child often does nothing.

.child {
flex: 1;
min-width: 0; /* the fix, in a row layout */
}
.column-child {
min-height: 0; /* the fix, in a column layout */
}

overflow: hidden on the item also works, because a non-visible overflow changes the automatic minimum size to zero.

Percentages in flex-basis need a definite container size

Section titled “Percentages in flex-basis need a definite container size”

flex-basis: 50% resolves against the container’s main size. In a column layout with no explicit height, that percentage may resolve to auto.

align-items: stretch fights explicit heights

Section titled “align-items: stretch fights explicit heights”

If an item has height: 200px, stretch cannot apply to it. Similarly align-items: center collapses an item to its content height — which is why a centred flex item sometimes looks smaller than you expect.

Nested flex containers need display: flex again

Section titled “Nested flex containers need display: flex again”

A flex item is not automatically a flex container. Set display: flex on it too if its own children need flex layout.

Absolutely positioned children are not flex items

Section titled “Absolutely positioned children are not flex items”

position: absolute removes a child from flex layout. It is positioned against the container (if the container is position: relative) using its static position, and justify-content/align-items still influence that static position — but it takes up no space.

They compose: gap sets a minimum space, space-between distributes what remains. With gap: 1rem; justify-content: space-between, gaps will be at least 1rem and larger when there is free space.

Text inputs have an intrinsic default width (around 20 characters) and will shrink below it. Give inputs min-width: 0 and let the wrapper control width, or set flex: 1.

Reach for flexbox when the content should determine the layout — a row of items of unknown count and unknown width. Reach for grid when the layout should determine where content goes — a defined set of rows and columns you place things into. Full comparison in Grid.

  • justify-* is the main axis, align-* is the cross axis; flex-direction swaps which is which.
  • flex: 1 means basis 0 (equal columns); flex: auto means basis auto (content-sized columns).
  • flex-grow distributes leftover space, not total width.
  • align-content needs flex-wrap: wrap to do anything.
  • margin-inline-start: auto pushes one item to the far end.
  • Flex items refuse to shrink below their content unless you set min-width: 0.
  • order breaks keyboard order — reorder the DOM instead for interactive content.