Skip to main content

CSS flexbox layout

CSS Flexbox is a layout model that arranges items along a single axis (row or column), automatically handling spacing, alignment, and sizing inside a container.

Theory

TL;DR

  • Flexbox is like a conveyor belt: items space out, align, and resize to fill the container without fixed positions
  • Main difference from floats: flexbox gives predictable alignment along two axes, main and cross
  • Use for 1D layouts (navbars, button groups); switch to CSS Grid for 2D (dashboards)
  • Default flex-direction is row (left to right)
  • justify-content controls the main axis; align-items controls the cross axis

Quick example

css
.container { display: flex; /* Turns children into flex items */ justify-content: space-between; /* Spreads items along main axis */ align-items: center; /* Centers on cross axis */ height: 100px; }

Three items inside .container spread horizontally with equal gaps, all vertically centered. No floats, no margin math.

Compared to float-based layouts

Before flexbox, vertical centering was famously painful. Float-based layouts wrapped unpredictably, and inline-block required white-space hacks. In production, I've seen more float layouts break from wrapping bugs than from almost anything else in CSS.

Flexbox replaces all of that with two properties. The container becomes a flexible unit. Items grow or shrink to fill available space along the main axis. You don't calculate margins for equal spacing; you tell the container how to distribute the free space.

The two axes

Every flex container has two axes:

  • Main axis - defined by flex-direction. Default is row (left to right).
  • Cross axis - perpendicular to the main axis.

When flex-direction: column, the main axis becomes vertical. This flips which property controls what: justify-content now aligns vertically, and align-items aligns horizontally. This trips up a lot of developers.

Container properties

PropertyWhat it does
flex-directionSets main axis: row, row-reverse, column, column-reverse
flex-wrapWhether items wrap: nowrap (default), wrap, wrap-reverse
justify-contentSpacing on main axis: flex-start, center, space-between, space-around, space-evenly
align-itemsAlignment on cross axis: stretch, center, flex-start, flex-end, baseline
align-contentDistributes multiple wrapped lines (only with flex-wrap: wrap)
gapSpace between items

Item properties

PropertyWhat it does
flex-growHow much an item grows relative to siblings (default: 0)
flex-shrinkHow much it shrinks (default: 1)
flex-basisInitial size before growing or shrinking (default: auto)
flexShorthand: flex-grow flex-shrink flex-basis
align-selfOverrides align-items for one item
orderChanges visual order without touching HTML

The shorthand flex: 1 expands to flex: 1 1 0%. That means "grow, shrink, start from zero width." This is how you get equal-width columns.

When to use

  • Simple row or column (navbar, button group) - flexbox
  • Content that needs to wrap (card grid, tag list) - flexbox with flex-wrap: wrap
  • Two-dimensional layout (dashboard, page grid) - CSS Grid instead
  • Replacing old float alignment bugs - flexbox cleans them up

How the browser handles this

When the browser parses display: flex, it creates a flex formatting context. It calculates free space along the main axis using flex factors from the flex shorthand, runs each item through a hypothetical main size calculation, then distributes leftover space via justify-content. Chrome's Blink engine resolves this in a single reflow pass, which is why flexbox is faster than float-based layouts for alignment work.

Common mistakes

Forgetting display: flex on the parent:

css
.container { /* no display: flex */ } .item { flex: 1; } /* Ignored - items still stack vertically */

flex: 1 on a child does nothing without the parent being a flex container. Add display: flex to .container.

Using align-items on the wrong axis in column direction:

css
.container { display: flex; flex-direction: column; align-items: center; /* Centers horizontally, NOT vertically */ }

In column direction, align-items controls horizontal alignment. To center items vertically in a column, use justify-content: center.

Not setting flex-wrap when items overflow:

css
.container { display: flex; flex-wrap: nowrap; width: 200px; } .item { width: 100px; } /* 5 items cause overflow */

nowrap is the default. With many items, they squeeze or overflow. Add flex-wrap: wrap to let them move to the next line.

Assuming equal widths without flex: 1:

css
.container { display: flex; } .item { /* default: flex: 0 1 auto - sizes by content */ }

Without flex: 1, items don't split space equally. They size based on their content. Use flex: 1 on items you want to grow equally.

min-width interaction with flex-grow:

css
.container { display: flex; width: 300px; } .item1 { flex: 1; min-width: 100px; } .item2 { flex: 2; }

item1 enforces its 100px minimum first, then item2 takes the rest (200px). Without min-width, item1 can shrink below what you expect.

Real-world usage

  • React navbars and headers (Material-UI App Bar uses flex internally)
  • Bootstrap .d-flex utility classes
  • Tailwind flex, justify-between, items-center for headers and cards
  • VS Code sidebar - flex for resizable panels

Follow-up questions

Q: What is the default flex-direction and how does row-reverse change it?
A: Default is row (left to right). row-reverse flips to right to left, reversing the main axis start and end. The DOM order stays the same; only the visual order changes.

Q: What is the difference between justify-content: space-between and space-around?
A: space-between puts equal space between items, nothing at the edges. space-around gives each item half the gap on each side, so edge items have half as much space from the container wall as items have between each other.

Q: How does align-content differ from align-items?
A: align-items aligns items within a single line on the cross axis. align-content distributes multiple wrapped lines. It only has an effect when flex-wrap: wrap creates more than one row.

Q: Given flex: 0 0 200px with margin: auto, what happens on a narrow container?
A: The item stays at 200px and will not shrink (no flex-shrink). margin: auto absorbs free space on the main axis, centering the item when space is available. If the container is narrower than 200px, the item overflows unless flex-wrap is set.

Examples

A typical navigation bar: logo on the left, links on the right.

html
<nav class="navbar"> <h1>Logo</h1> <ul> <li>Home</li> <li>About</li> <li>Contact</li> </ul> </nav>
css
.navbar { display: flex; justify-content: space-between; /* Logo left, nav right */ align-items: center; /* Both vertically centered */ padding: 0 20px; background: #333; } .navbar ul { display: flex; /* Nested flex: menu items in a row */ list-style: none; gap: 1rem; }

Nested flex containers work fine. The ul becomes its own flex container for the li items inside it.

Sidebar and main content

A page layout with a fixed sidebar and fluid main area.

css
.layout { display: flex; min-height: 100vh; } .sidebar { flex: 0 0 250px; } /* Fixed 250px, no grow, no shrink */ .main { flex: 1; } /* Takes all remaining width */ .aside { flex: 0 0 200px; } /* Fixed 200px on the right */

0 0 250px means "start at 250px, stay there." flex: 1 means "take what is left." That combination is one of the most common flex patterns in production.

Footer always at the bottom, even on short pages.

css
body { display: flex; flex-direction: column; /* Header, main, footer stack vertically */ min-height: 100vh; } main { flex: 1; /* Grows to fill remaining space, pushing footer down */ }

flex: 1 on main is the key. It grows to absorb all available vertical space, so the footer stays at the bottom regardless of how much content the page has.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?