C.W.K.
Stream
Lesson 01 of 04 · published

The Flexbox Mental Model: One Axis At A Time

~12 min · flexbox, main-axis, cross-axis, flex-direction, flex-wrap

Level 0Markup Novice
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Flexbox is one-dimensional. Pick the axis you care about, and Flexbox makes everything along that axis behave."

What "One-Dimensional" Actually Means

Flexbox arranges its children along a single axis at a time — either a row or a column. The children can wrap to additional lines, but conceptually you're always thinking about "a line of items." Compare this to Grid, which thinks in rows AND columns simultaneously.

This matters for the mental model: before you write Flexbox, decide whether the layout is fundamentally a row or a column. If you can answer that cleanly, Flexbox is the right tool. If you need precise control over both rows AND columns at once, you want Grid.

The Two Axes (And Why Their Names Matter)

Flexbox names alignment by axis, not by direction:

  • Main axis — the axis the children flow along. Determined by flex-direction.
  • Cross axis — perpendicular to the main axis.

Default flex-direction: row → main is horizontal (left-to-right in LTR), cross is vertical. Set flex-direction: column → main becomes vertical, cross becomes horizontal. The properties don't change names; their effective direction does.

This is why justify-content (acts on main) and align-items (acts on cross) work the same in both row and column flex containers — the axis they refer to swaps automatically.

The Four flex-direction Values

  • row — default. Main axis = inline direction (LTR: left-to-right).
  • row-reverse — main axis reverses. Items flow right-to-left (in LTR).
  • column — main axis = block direction (top-to-bottom).
  • column-reverse — main axis reverses. Items flow bottom-to-top.

The -reverse variants are useful for: "newest first" chat lists (column-reverse keeps the latest at the bottom of source order but visually at the top), or RTL-aware layouts that need explicit reversal.

Wrapping: flex-wrap

By default, flex items try to fit on one line, even if they overflow the container. flex-wrap: wrap allows them to wrap to multiple lines when they don't fit:

The flex-flow Shorthand

flex-flow: row wrap sets flex-direction and flex-wrap in one declaration. Convenient when you're using both.

Gap: The Modern Spacing Property

gap: 1rem on a flex container puts 1rem of space between every pair of children. row-gap and column-gap control the two axes independently. This replaced the old margin-based spacing patterns (negative margins on the container, margins on every child except the last) entirely.

Use gap for flex spacing. Reserve margin for individual element offsets. gap doesn't cause margin collapse, works regardless of which child is first or last, and stays correct under inserts/removes. Two lines of CSS replace what used to be a dozen.

"But what about older browsers?" — gap on flex is supported in all browsers shipped after late 2021. By 2026, the question doesn't come up.

Decision Tree: Flexbox or Grid?

  • A row of buttons, navbar items, chips — Flexbox. One dimension. Wrap allowed.
  • A stack of cards, a sidebar with sections — Flexbox column.
  • Header above main above footer — Flexbox column, main with flex: 1.
  • A photo grid where each row has a specific number of columns — Grid.
  • A magazine-style layout with a sidebar, main, header, footer at specific positions — Grid (with template-areas).
  • Equal-width columns that share leftover space — Flexbox with flex: 1 on each.
  • Columns of specific widths (e.g., a 240px sidebar + flexible main) — Either works; Grid grid-template-columns: 240px 1fr is the tightest.

What Flexbox Won't Do (And Why It's OK)

Flexbox doesn't align items along two axes at once with precision. If you want item A in row 2, column 3 and item B in row 1, column 1 of the same container, that's Grid. Flexbox arranges items along the main axis in source order; the cross-axis alignment is for the whole line, not individual placement.

Flexbox also doesn't have explicit row/column "tracks" the way Grid does. Items wrap when they overflow, but you can't say "this item goes in row 3." If you find yourself wanting that, you want Grid.

Pippa's Note

Pippa's chat panel is a Flexbox column: messages list with flex: 1 in the middle (grows to fill available height, scrolls when content overflows), composer at the bottom (auto height), header at the top. Three children, one container, one direction. The conversation sidebar is a Flexbox column with conversation rows. Every horizontal arrangement (action button rows, tag chips, the brain picker in council) is a Flexbox row with gap. Almost every UI surface in the WebUI is a flex tree — Grid is reserved for the few page-level layouts where two-dimensional precision earns its keep.

Code

flex-direction in three contexts·css
/* Default flexbox: row */
.nav {
  display: flex;
  gap: 1rem;
  /* main axis: horizontal
     cross axis: vertical
     items flow left-to-right */
}

/* Flex column: stack vertically */
.sidebar {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  /* main axis: vertical
     cross axis: horizontal
     items stack top-to-bottom */
}

/* Reverse for chat messages (newest at bottom of source) */
.chat-history {
  display: flex;
  flex-direction: column-reverse;
  /* Items in source order appear visually reversed.
     Useful when JS appends new messages but you want
     them to show at the top. */
}
Wrapping + gap·css
/* Wrapping a row of cards */
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.card-row > .card {
  flex: 1 1 240px;     /* grow, shrink, basis 240px */
  /* Each card wants to be 240px, but can grow to fill the row.
     If 4 cards don't fit, they wrap to a new line and each
     card stretches to fill the row width. */
}

/* flex-flow shorthand */
.card-row {
  display: flex;
  flex-flow: row wrap;  /* flex-direction + flex-wrap */
  gap: 1rem;
}

/* gap with separate row and column control */
.gallery {
  display: flex;
  flex-flow: row wrap;
  row-gap: 2rem;        /* between rows when wrapped */
  column-gap: 1rem;     /* between siblings on same row */
}
Real layouts, decided by axis·css
/* The mental model in code: deciding row vs column */

/* A button group → flex row */
.btn-group {
  display: flex;
  gap: 0.5rem;
}

/* A form's vertical stack → flex column */
.form-fields {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Sticky footer page layout → flex column */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  margin: 0;
}
main {
  flex: 1;             /* grows to fill remaining height */
}

/* A masonry-ish grid with wrapping cards → flex row + wrap */
.gallery {
  display: flex;
  flex-flow: row wrap;
  gap: 1rem;
}
.gallery > * {
  flex: 1 1 200px;     /* every card flexes from 200px */
}

External links

Exercise

Build a sticky-footer page layout: <body> as a flex column with min-height: 100vh, <main> with flex: 1, <header> and <footer> with default size. Verify in DevTools (resize the viewport, add content to main, confirm the footer behavior). Then convert a navbar with 5 items to a flex row with gap: 1rem and a logo that uses margin-right: auto to push the remaining items to the right.
Hint
margin: auto inside a flex container consumes available space along the main axis — perfect for 'push the rest to the right' patterns. flex: 1 on main is what makes the footer sticky.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.