Flexbox
Laying out items in a row or column with flexbox: the flex container and items, the main and cross axis, justify-content and align-items, gap, wrapping, and flex-grow, flex-shrink and flex-basis.
What flexbox is for
Flexbox (the CSS Flexible Box Layout module) arranges elements along a single line: a row of menu links, a toolbar of buttons, a label next to an input, a column of cards. It works in one dimension at a time and lets the items share the available space: they can grow, shrink, be spaced out or be aligned, all without knowing their sizes in advance.
Before flexbox, these layouts were built with floats,
inline-block and margin tricks, and centering something
vertically was a running joke. Flexbox has worked in every major
browser for years and is the everyday tool for component layout. For
two-dimensional page layouts with rows and columns, use
CSS Grid.
The flex container and its items
Setting display: flex on an element makes it a
flex container. Its
direct children become
flex items and are laid out by flexbox;
grandchildren are not affected (they follow normal flow inside their
parent, unless that parent is a flex container too). The container
itself still behaves like a block on the outside. Use
display: inline-flex for a container that sits inside a
line of text.
The menu of the Kestrel Energy portal is a list, which tells screen readers how many links there are. One declaration turns the vertical list into a row:
<nav aria-label="Portal">
<ul class="menu">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Meter readings</a></li>
<li><a href="#">Invoices</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
.menu {
display: flex; /* the ul is the container, each li an item */
gap: 1.5rem;
list-style: none;
padding: 0;
}
Flex items are "blockified": a span or
a that is a flex item accepts width,
height and vertical margins, as a block would. And
margins between flex items never
collapse.
The main axis and the cross axis
Every flex container has two axes. The
main axis is the direction in which
the items are placed one after another, set by
flex-direction. The
cross axis runs at right angles to
it. Almost every flexbox property works along one of the two, so
knowing which is which is the key to the whole module.
flex-direction turns the axes: in a row the main axis
is horizontal, in a column it is vertical. (In a right-to-left
language a row runs from right to left.)
flex-direction has four values: row (the
default, in the text direction), column (top to
bottom), and row-reverse and
column-reverse, which flip the start and end.
<ol class="row">
<li>Read meter</li>
<li>Enter number</li>
<li>Confirm</li>
</ol>
<ol class="column">
<li>Read meter</li>
<li>Enter number</li>
<li>Confirm</li>
</ol>
ol { display: flex; gap: 0.5rem; list-style: none; padding: 0; margin: 0 0 1rem; }
.column { flex-direction: column; }
li { border: 1px solid steelblue; background: aliceblue; padding: 0.5rem; }
Notice that in the column the items are as wide as the container.
That is the default align-items: stretch at work on the
cross axis, explained below.
Distributing items: justify-content
justify-content decides what happens to the free space on the main axis, the space left over when the items do not fill the line:
-
flex-start(the default): items packed at the start. flex-end: packed at the end.center: packed in the middle.-
space-between: first and last item against the edges, equal space between the others. -
space-around: equal space around each item, so the edges get half as much as the gaps between items. -
space-evenly: exactly equal spaces, including at both edges.
<p class="label">flex-start</p>
<div class="bar start"><span>Day</span><span>Night</span><span>Total</span></div>
<p class="label">center</p>
<div class="bar center"><span>Day</span><span>Night</span><span>Total</span></div>
<p class="label">space-between</p>
<div class="bar between"><span>Day</span><span>Night</span><span>Total</span></div>
<p class="label">space-evenly</p>
<div class="bar evenly"><span>Day</span><span>Night</span><span>Total</span></div>
.label { margin: 0; font-family: monospace; }
.bar { display: flex; border: 1px dashed gray; margin: 0.25rem 0 1rem; }
.bar span { background: lightsteelblue; padding: 0.25rem 0.75rem; }
.start { justify-content: flex-start; }
.center { justify-content: center; }
.between { justify-content: space-between; }
.evenly { justify-content: space-evenly; }
A common pattern is a header with the logo on the left and the menu
on the right: justify-content: space-between on the
header, with the two as its items. Another is
margin-left: auto on one item, which pushes it and
everything after it to the end, for example a "Log out" link at the
far right of a menu.
Aligning items: align-items
align-items aligns the items on the cross axis, inside their line:
-
stretch(the default): items stretch to fill the line's height (in a row) or width (in a column), unless they have a fixed size. This is why flex cards in a row are equally tall. -
flex-start,flex-end,center: aligned to the start, the end or the middle of the cross axis. -
baseline: aligned so the first lines of text sit on the same baseline, useful when items have different font sizes.
A single item can override the container's setting with
align-self. In the banner below the icon is much larger
than the text; align-items: center lines them up
through the middle:
<div class="banner">
<span class="icon" aria-hidden="true">⚡</span>
<p>Planned maintenance on Sunday, 02:00 to 04:00.</p>
<a href="#">Details</a>
</div>
.banner {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.5rem 1rem;
border: 1px solid steelblue;
background: aliceblue;
}
.banner p { margin: 0; }
.icon { font-size: 2rem; }
Centering in both directions
With both properties set to center, an item sits in the
exact middle of its container, whatever the sizes:
<div class="stage">
<p class="message">No outages reported in your area.</p>
</div>
.stage {
display: flex;
justify-content: center; /* main axis: horizontal */
align-items: center; /* cross axis: vertical */
min-height: 8rem;
border: 1px dashed gray;
}
.message { margin: 0; }
The container needs a height larger than its content for vertical
centering to show; here min-height provides it.
Space between items: gap
The gap property sets the space
between items in a flex or grid container, not at the outer
edges. gap: 1rem sets both directions;
row-gap and column-gap set them separately
(they matter when items wrap onto several lines). Gap in flexbox has
been Baseline widely available since October 2023.
Before gap worked in flexbox, the space came from
margins on the items, followed by an extra rule to remove the margin
of the last item, and negative margins on the container when items
wrapped. gap replaces all of that: it only ever appears
between items, and it never collapses or doubles.
Wrapping onto several lines
By default all items stay on one line (flex-wrap: nowrap) and are squeezed when there is not enough room, down to the width
of their longest word, and then they overflow.
flex-wrap: wrap lets items move to a new line instead.
Each line is then laid out as its own little flex row.
Combined with a minimum size per item, that gives a set of cards that rearranges itself for the available width, without media queries. The frame below is as wide as this page's text column: make your browser window narrower and the cards wrap onto more lines.
<div class="plans">
<article class="plan">
<h2>Fixed</h2>
<p>The same price per kWh for three years.</p>
</article>
<article class="plan">
<h2>Variable</h2>
<p>The price follows the market and is updated every month. No fixed term.</p>
</article>
<article class="plan">
<h2>Dynamic</h2>
<p>A new price every hour.</p>
</article>
</div>
.plans {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.plan {
flex: 1 1 12rem; /* grow, shrink, start from 12rem */
border: 1px solid gray;
padding: 0 1rem;
}
.plan h2 { font-size: 1.125rem; }
All three cards are equally tall in each line, because of
align-items: stretch, even though their text has a
different length. When the items wrap onto several lines,
align-content distributes the lines on the
cross axis, the way justify-content distributes items
on the main axis.
flex-grow, flex-shrink and flex-basis
Three properties on the items decide how big each one is along the main axis. The browser starts from each item's basis, adds up the bases, and compares the total with the container: if there is space left, the items grow; if there is too little, they shrink.
flex-basis: the starting size
flex-basis is an item's starting
size on the main axis, before any free space is shared out or taken
away. The default, auto, means "use my
width (or height in a column) if set,
otherwise the size of my content". A length such as
12rem or 0 overrides that.
flex-grow: sharing out free space
flex-grow is a number that says how
large a share of the free space an item receives. The default is
0: the item does not grow. Free space is divided in
proportion to the numbers. In the example below the container is
320px wide and each item starts at 40px, so 320 − 3 × 40 = 200px is
free. The shares are 1 : 1 : 2, so the items get 50, 50 and 100px
extra: they end up 90, 90 and 140px wide.
<div class="track">
<div class="a">grow 1</div>
<div class="b">grow 1</div>
<div class="c">grow 2</div>
</div>
.track { display: flex; width: 320px; outline: 1px dashed gray; }
.track div {
box-sizing: border-box;
flex-basis: 40px;
border: 1px solid steelblue;
background: aliceblue;
padding: 0.5rem 0;
text-align: center;
}
.a, .b { flex-grow: 1; }
.c { flex-grow: 2; }
Note that flex-grow: 2 does not make an item twice as
wide as one with flex-grow: 1; it gets twice
as much of the free space. Only when every basis is
0 are the final widths in the same ratio as the grow
numbers.
A real use: a search form where the input takes all the space the label and button leave over.
<form class="search" role="search">
<label for="q">Search help</label>
<input id="q" type="search" name="q">
<button type="submit">Search</button>
</form>
.search { display: flex; align-items: center; gap: 0.5rem; }
.search input { flex-grow: 1; min-width: 0; }
.search input, .search button { font: inherit; padding: 0.25rem 0.5rem; }
flex-shrink: giving back space
flex-shrink works the other way round: when the items
do not fit, it says how much each one gives up. The default is
1, so all items shrink, larger ones more. Set
flex-shrink: 0 on an item that must keep its size, such
as an icon or an avatar.
Items will not shrink below their
minimum content size, usually the width of their
longest word, because the default min-width of a flex
item is auto. A long URL or a child with
white-space: nowrap can therefore push a flex item out
of its container. The fix is min-width: 0 on the item
(as on the search input above), which allows it to shrink fully.
The flex shorthand
In practice the three properties are set together with
flex: grow shrink basis. A few values cover almost
every case:
| Shorthand | Means | Use for |
|---|---|---|
flex: initial |
0 1 auto (the default) |
Content-sized items that may shrink |
flex: 1 |
1 1 0% |
Items that share all the space equally |
flex: auto |
1 1 auto |
Items that grow, starting from their content size |
flex: none |
0 0 auto |
Items that keep their size: no growing, no shrinking |
flex: 1 1 12rem |
Grow and shrink from 12rem | Wrapping cards, as in the plans example |
Visual order and source order
The order property moves an item along the main axis
(lower numbers first; the default is 0), and
row-reverse or column-reverse flip the
whole line. Both change only what is drawn. The
keyboard focus order and the order in which screen readers read the
content still follow the HTML. Press Tab in the result
below: after "2. Enter reading" the focus jumps back to "3. Confirm"
at the far left.
<nav class="steps" aria-label="Steps">
<a href="#">1. Log in</a>
<a href="#">2. Enter reading</a>
<a href="#" class="moved">3. Confirm</a>
</nav>
.steps { display: flex; gap: 1rem; }
.moved { order: -1; } /* drawn first, but still focused last */
When the visual order and the source order disagree, keyboard and
screen reader users get a different page from sighted mouse users.
WCAG success criteria 1.3.2 (Meaningful Sequence) and 2.4.3 (Focus
Order) exist for exactly this. Put the HTML in the order that makes
sense and use order only for small visual tweaks that
do not change the meaning. (A reading-flow property
that makes focus follow the visual order is being developed, but as
of 2026 only Chromium-based browsers support it.)
Flexbox or grid?
Both are layout modes for a container's children, and they share
gap, justify-content and
align-items. The difference is the number of
dimensions:
- Flexbox lays out in one direction and lets the content decide the sizes. Items in one line know nothing about items in the next line, so wrapped cards do not line up in columns. Good for menus, toolbars, button groups, a label next to a field, and centering.
- CSS Grid defines rows and columns together, and items line up in both directions. Good for page layouts, card grids with straight columns, and forms with aligned labels.
They combine well: a grid for the page, flexbox inside the header and the cards. See also Responsive Design for changing the layout at different screen sizes.