RizTech Academy logo
RizTech Academy
LayoutLesson 2 of 640 min

Flexbox, properly explained

Flexbox lays things out along one axis. That is the whole idea, and nearly every Flexbox confusion is a case of forgetting which axis you are on.

.row {
  display: flex;
  gap: 1rem;
}

Three lines and you have a horizontal row with even spacing, no whitespace gap, and equal-height items. That last part is free and used to be very hard.

The two axes

flex-direction: row;             /* default: main axis is horizontal */
flex-direction: column;          /* main axis is vertical */
flex-direction: row-reverse;
flex-direction: column-reverse;

The main axis is the one flex-direction names. The cross axis is the other one.

This matters because the alignment properties are named after axes, not directions:

  • justify-content aligns along the main axis.
  • align-items aligns along the cross axis.

So in a row, justify-content is horizontal and align-items is vertical. Switch to column and they swap. Every "why is my centring not working" question is this.

The memory hook: justify-content follows flex-direction. If direction is row, justify is horizontal. If direction is column, justify is vertical.

Properties on the container

.container {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  align-content: flex-start;
  gap: 1rem 2rem;              /* row-gap column-gap */
}

justify-content

justify-content: flex-start;    /* default */
justify-content: flex-end;
justify-content: center;
justify-content: space-between; /* first at the start, last at the end */
justify-content: space-around;  /* equal space around each, so half at the edges */
justify-content: space-evenly;  /* genuinely equal gaps, including the edges */

space-between is the one you will use for a header: logo on the left, nav on the right, in two lines of CSS.

The distinction between space-around and space-evenly catches people: space-around gives each item equal space on both sides, so the gap between two items is double the gap at the edge. space-evenly makes every gap identical.

align-items

align-items: stretch;      /* default — items fill the cross axis */
align-items: center;
align-items: flex-start;
align-items: flex-end;
align-items: baseline;      /* align their text baselines */

stretch being the default is why flex items in a row come out the same height automatically. It is also why a card you expected to hug its content fills the row's height — set align-items: flex-start if you do not want that.

baseline is the underused one. For a row of a price and a label at different font sizes, baseline aligns the text rather than the boxes, which is what looks right.

flex-wrap

flex-wrap: nowrap;    /* default — items shrink instead of wrapping */
flex-wrap: wrap;

The default is nowrap, and it is the cause of the most common Flexbox frustration: items squashed into unreadable slivers on a phone rather than wrapping. flex-wrap: wrap is usually what you meant.

Once items wrap, align-content controls how the lines are distributed on the cross axis — it does nothing with a single line, which is why it appears not to work.

gap

gap: 1rem;

gap replaced the whole genre of margin-right plus :last-child { margin-right: 0 }. It applies only between items, never at the edges, and it does not collapse. Use it.

Properties on the items

.item {
  flex-grow: 0;
  flex-shrink: 1;
  flex-basis: auto;
  flex: 0 1 auto;          /* the shorthand: grow shrink basis */
  align-self: center;      /* override align-items for one item */
  order: 2;
}

flex, the shorthand worth learning by its three common values

flex: 1;        /* = 1 1 0%   — share the space equally, ignore content width */
flex: auto;     /* = 1 1 auto — grow, but start from the content's width */
flex: none;     /* = 0 0 auto — do not grow or shrink */

The difference between flex: 1 and flex: auto is the one to understand, because it decides whether your columns are equal.

.equal > * { flex: 1; }      /* three genuinely equal columns */
.natural > * { flex: auto; } /* wider content gets a wider column */

flex: 1 sets flex-basis: 0%, so every item starts from zero and the free space is divided equally — equal widths regardless of content. flex: auto starts from the content width and shares only the leftover, so an item with more text stays wider.

For a sidebar-plus-content layout:

.layout { display: flex; gap: 2rem; }
.sidebar { flex: 0 0 240px; }    /* fixed: do not grow, do not shrink, 240px */
.content { flex: 1; }            /* takes everything else */

That is the pattern, and flex: 0 0 240px is more reliable than width: 240px, because a flex item's width can still be shrunk by flex-shrink: 1.

The min-width: 0 problem

The Flexbox bug that costs people the most time:

<div class="row">
  <div class="text">A very long unbroken string like a URL or an email address…</div>
  <div class="side">Side</div>
</div>

The text refuses to shrink and pushes the row wider than its container. overflow does nothing. max-width: 100% does nothing.

The cause: a flex item's default min-width is auto, which means "at least as wide as my content's minimum size" — and for an unbroken string, that is the whole string. flex-shrink cannot go below that floor.

.text {
  min-width: 0;
}

That one declaration fixes it. In a column, the equivalent is min-height: 0. Combine it with overflow-wrap: break-word and you handle long strings properly.

This is worth committing to memory: if a flex item will not shrink, set min-width: 0.

order, and why to be careful

.item { order: -1; }     /* moves it first */

order changes the visual order only. The DOM order is unchanged, so keyboard focus still follows the HTML. Reorder things visually and a keyboard user tabs around the screen in an order that makes no sense, which is a WCAG failure.

Use it for small adjustments at a breakpoint where the reading order still works. Do not use it to restructure a page — fix the HTML instead.

Centring, finally

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

Horizontally and vertically centred, in four lines. Before Flexbox this was a known hard problem with a dozen partial answers, and it is worth knowing that so you appreciate why people were pleased.

There is also place-items: center, which is shorthand for align-items and justify-items — but note justify-items has no effect in Flexbox, so place-items: center in a flex container centres on the cross axis only. It works as expected in Grid.

Two patterns you will use constantly

A header:

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

A card with a footer pinned to the bottom, regardless of how much text is above:

.card {
  display: flex;
  flex-direction: column;
}
.card__body { flex: 1; }       /* absorbs the spare height */
.card__footer { margin-top: auto; }  /* or this, instead */

margin-top: auto on the last item pushes it to the bottom — auto margins eat all the free space on the main axis. The same trick with margin-left: auto in a row pushes one item to the right, which is often tidier than restructuring for space-between.

Check your work

What Flexbox is for. One axis.

justify-content versus align-items. Main axis and cross axis — they swap when flex-direction changes.

Why flex items come out the same height. align-items: stretch is the default.

space-around versus space-evenly. Equal space around each item, so edges are half; versus genuinely equal gaps.

The default of flex-wrap. nowrap — items squash rather than wrap.

When align-content does nothing. With a single line.

flex: 1 versus flex: auto. Basis 0%, so equal widths; versus basis auto, so content affects width.

The reliable way to fix a sidebar's width. flex: 0 0 240px.

Why a flex item will not shrink. Its default min-width: auto is its content's minimum. Fix with min-width: 0.

Why order is risky. It changes visual order only, so tab order no longer matches.

What margin-top: auto does in a column. Pushes the item to the bottom by eating the free space.

Why place-items: center only half works in Flexbox. justify-items has no effect there.

Practice

  1. Make a row of three boxes with display: flex and gap. Note that the HTML whitespace gap is gone.
  2. Centre them with justify-content: center, then switch to flex-direction: column and predict what happens before looking.
  3. Try all six justify-content values. Photograph space-around and space-evenly side by side.
  4. Put three cards with different amounts of text in a row. Note their equal heights, then add align-items: flex-start.
  5. Align a large price and a small label with align-items: center, then baseline.
  6. Put eight items in a row on a 360px viewport with the default nowrap. Then add wrap.
  7. Add align-content: center with one line, then with three.
  8. Build three columns with flex: 1, then flex: auto, with different text lengths. Measure the widths.
  9. Build a 240px sidebar with width: 240px and shrink the window until it squashes. Change to flex: 0 0 240px.
  10. Put a 200-character unbroken string in a flex row and watch it break the layout. Fix it with min-width: 0.
  11. Use order: -1 on the third item, then tab through the row and note the order.
  12. Centre a box both ways with four lines of CSS.
  13. Build a card with margin-top: auto on its footer and vary the body text length.
  14. Push one item to the right of a row with margin-left: auto.
  15. Turn on the Flexbox overlay in devtools (the flex badge next to the element) and watch the axes as you change flex-direction.

Official documentation

Next: Grid, for when one axis is not enough.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship