Flexbox: One Dimension at a Time

Article · 12:00

Flexbox lays things out along a single axis - a row or a column - and shares the leftover space between them. That is the whole model, and almost every problem people have with it comes from expecting it to handle two axes at once.

This lesson has 1 runnable example. Press Try yourself! under any of them to open it in the editor.

Flexbox: One Dimension at a Time

Flexbox lays things out along a single axis - a row or a column - and shares the leftover space between them. That is the whole model. Almost every problem people have with it comes from expecting it to handle two axes at once, which is Grid's job.

The two axes, and why the names are abstract

.row { display: flex; }                           /* main axis: horizontal */
.col { display: flex; flex-direction: column; }   /* main axis: vertical */
  • The main axis is the one flex-direction names.
  • The cross axis is the other one.

The names are abstract on purpose: set flex-direction: column and every property below swaps meaning without you rewriting any of them. justify-content always works along the main axis, whichever that currently is.

This is also why justify-content and align-items are so often mixed up. The fix is not to memorise which is horizontal - neither is. Learn them as "along the flow" and "across the flow".

Properties on the container

.container {
  display: flex;
  flex-direction: row;          /* row | row-reverse | column | column-reverse */
  flex-wrap: wrap;              /* nowrap (default) | wrap */
  gap: 1rem;                    /* space between items, both axes */

  justify-content: space-between;  /* along the main axis */
  align-items: center;             /* across the cross axis, per line */
  align-content: flex-start;       /* the lines themselves, when wrapped */
}

justify-content takes: flex-start, flex-end, center, space-between, space-around, space-evenly.

The three space values differ in where the gaps go:

space-between   |A      B      C|     no space at the ends
space-around    |  A    B    C  |     half-size space at the ends
space-evenly    |   A   B   C   |     equal space everywhere

align-items takes: stretch (the default - items fill the cross axis), flex-start, flex-end, center, baseline.

baseline is the underrated one: it lines up the text baselines of items with different font sizes, which is what you want for a heading next to a badge.

gap replaced the old approach of putting a margin on every item and removing it from the last. It applies between items only, never at the edges, and it works in grid too.

Properties on the items

.item {
  flex-grow: 0;      /* share of EXTRA space. 0 = do not grow */
  flex-shrink: 1;    /* share of the DEFICIT. 0 = do not shrink */
  flex-basis: auto;  /* starting size before growing or shrinking */

  flex: 0 1 auto;    /* the shorthand, in that order - and the default */

  align-self: center;  /* override align-items for one item */
  order: 2;            /* visual order only - see the warning below */
}

The shorthand values worth knowing

flex: 1;        /* = 1 1 0%   - share space equally, ignore content size */
flex: auto;     /* = 1 1 auto - share space, but start from content size */
flex: none;     /* = 0 0 auto - do not grow or shrink. Fixed. */
flex: 0 0 240px;/* exactly 240px, never flexing */

The difference between flex: 1 and flex: auto is the one that matters and the one people miss.

With flex: 1, the basis is 0%, so content size is ignored entirely and every item ends up the same width regardless of what is in it.

With flex: auto, the basis is the content size, so items start at their natural width and then share the leftover - an item with more text in it ends up wider.

/* Three equal columns, whatever is in them */
.equal > * { flex: 1; }

/* Columns proportional to their content, sharing what is left */
.natural > * { flex: auto; }

Four layouts that cover most of what you need

A bar with something pushed to the end

.bar { display: flex; align-items: center; gap: 1rem; }
.bar .spacer { margin-inline-start: auto; }

margin: auto in a flex container absorbs all the free space on that side. One margin-inline-start: auto pushes that item and everything after it to the end - which is often clearer than restructuring to use justify-content: space-between.

A sidebar that holds its width

.layout { display: flex; gap: 2rem; }
.sidebar { flex: 0 0 18rem; }   /* fixed */
.main    { flex: 1; min-width: 0; }  /* takes the rest */

min-width: 0 on the main column is not optional. A flex item's min-width defaults to auto, which means it refuses to shrink below its content's intrinsic minimum - so one long code block or one unbroken URL pushes the sidebar off the screen. This is the single most common flexbox bug and the fix is never obvious from the symptom.

Cards that wrap and fill the last row

.cards { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.cards > * { flex: 1 1 18rem; }

Each card wants to be at least 18rem, grows to share the row, and wraps when there is not room. The one caveat is that items on a partly-filled last row stretch to fill it, which can look odd - and is the case where Grid's auto-fill is the better tool.

Vertical centring

.centre { display: flex; align-items: center; justify-content: center; min-height: 100dvh; }

The problem that defined a decade of CSS, in three declarations.

All four, in one page you can run

HTML Example 1 88 lines
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Four flex layouts</title>
    <style>
      *, *::before, *::after { box-sizing: border-box; }
      body {
        font: 16px/1.6 system-ui, sans-serif;
        margin: 0;
        padding: 1.5rem;
        background: #fbfbfd;
        color: #16181d;
      }
      h2 { font-size: 1rem; margin: 2rem 0 0.5rem; color: #5c626e; }
      .box {
        background: #fff;
        border: 1px solid #e3e5ea;
        border-radius: 10px;
        padding: 0.75rem 1rem;
      }

      /* 1. A bar with something pushed to the end. margin-left: auto
         absorbs all the free space, so no spacer element is needed. */
      .bar {
        display: flex;
        align-items: center;
        gap: 1rem;
      }
      .bar .brand { font-weight: 600; }
      .bar nav { display: flex; gap: 1rem; }
      .bar .account { margin-inline-start: auto; }

      /* 2. A sidebar that holds its width while the main column takes
         the rest. flex: 0 0 14rem is "do not grow, do not shrink,
         start at 14rem"; min-width: 0 on the main column stops long
         content from forcing it wider than its share. */
      .with-sidebar { display: flex; gap: 1rem; flex-wrap: wrap; }
      .with-sidebar .side { flex: 0 0 14rem; }
      .with-sidebar .main { flex: 1 1 20rem; min-width: 0; }

      /* 3. Cards that wrap, where the last row fills rather than
         leaving a gap. flex-basis gives the preferred width; grow
         lets the survivors of a wrap share what is left. */
      .cards { display: flex; flex-wrap: wrap; gap: 0.75rem; }
      .cards > * { flex: 1 1 12rem; }

      /* 4. Vertical centring, which used to be the hard one. */
      .hero {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 9rem;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <h2>1 - a bar, with the account pushed to the end</h2>
    <div class="bar box">
      <span class="brand">Omni</span>
      <nav><a href="#">Courses</a><a href="#">Paths</a></nav>
      <span class="account">Signed in</span>
    </div>

    <h2>2 - a sidebar that does not move</h2>
    <div class="with-sidebar">
      <aside class="side box">Fixed at 14rem until the row runs out of room.</aside>
      <div class="main box">
        Takes everything left over. Drag the window narrow and the sidebar
        wraps above this column rather than squeezing it.
      </div>
    </div>

    <h2>3 - cards that fill the last row</h2>
    <div class="cards">
      <div class="box">One</div>
      <div class="box">Two</div>
      <div class="box">Three</div>
      <div class="box">Four</div>
      <div class="box">Five</div>
    </div>

    <h2>4 - centred both ways</h2>
    <div class="hero box">Centred, in two declarations.</div>
  </body>
</html>

Open this example in the editor and change it. Nothing you type is saved.

Drag the window from wide to narrow and watch each one in turn. The bar keeps the account at the end without a spacer. The sidebar holds 14rem and then wraps rather than collapsing. The cards reflow and the last row spreads to fill. And the hero stays centred at every width.

The accessibility warning about order

.item { order: -1; }          /* moves it first, visually */
.row { flex-direction: row-reverse; }

Both change the visual order only. The DOM order is unchanged, so:

  • The tab order still follows the source. A keyboard user tabs from the visually-last item to the visually-first, which looks like a bug.
  • A screen reader reads the source order, so the page is narrated in a different order from the one it is read in.

Use these for genuinely cosmetic reordering at a breakpoint, and fix the source order when the order carries meaning. If reordering the DOM is awkward, that is usually a signal the markup is structured around the layout rather than the content.

Flex or Grid

The honest rule:

  • Flex when the content decides the layout - a row of buttons, a navigation bar, a card's internals, anything that should wrap naturally.
  • Grid when the layout decides where content goes - a page skeleton, a form of label-and-field pairs, anything where things must line up across rows as well as along them.

"One dimension versus two" is the usual phrasing and it is slightly misleading, because flex items do wrap onto several lines. The sharper distinction is alignment across lines: in flex, each line is laid out independently, so items in the second row do not line up with items in the first. In grid they do, because the tracks are defined up front.

What goes wrong

Symptom Cause Fix
A long word pushes everything out Flex item min-width: auto min-width: 0
Items are all the same width despite different content flex: 1 zeroes the basis flex: auto
align-items: center does nothing Only one line and the container has no extra cross-axis size Give the container a height
Second row does not line up with the first Flex lines are independent Use Grid
Tab order jumps around order or row-reverse Fix the source order
The gap appears at the edges too Margins instead of gap Use gap
An item will not shrink flex-shrink: 0, or an image with no min-width: 0

The mental model to keep

A flex container has a main axis. Items are placed along it in order. If there is space left over, flex-grow says who takes it; if there is not enough, flex-shrink says who gives it up; justify-content positions whatever is left; align-items handles the other direction.

Everything else in this lesson follows from those five sentences.