CSS Introduction to Flexbox

Flexbox (the Flexible Box Layout) is a CSS layout model designed to arrange items in a single row or column, automatically handling their sizing, spacing, and alignment even when their size is unknown or dynamic. Before Flexbox, developers relied on floats, inline-block hacks, and absolute positioning to build things like navigation bars, card rows, and centered content — all of which were fragile and required extra markup. Flexbox turns layout into a small set of declarative properties applied to a container and its direct children, and it is one of the two foundational modern CSS layout systems (the other being Grid). Understanding it thoroughly unlocks the vast majority of everyday component layout work.

Overview / How it works

Flexbox works by turning an element into a flex container using display: flex (or display: inline-flex). The moment you do this, every direct child of that element becomes a flex item, and the browser’s rendering engine stops laying those children out using the normal block/inline flow. Instead, it lays them out along a single axis, called the main axis, with a perpendicular cross axis available for alignment.

The direction of the main axis is controlled by flex-direction. With the default value, row, the main axis runs horizontally (left to right in a left-to-right document) and the cross axis runs vertically. With column, the main axis runs vertically and the cross axis runs horizontally. This distinction matters enormously: properties like justify-content always operate on the main axis, while align-items and align-content always operate on the cross axis — regardless of which physical direction that happens to be.

Internally, the rendering engine performs a multi-pass layout: first it determines each item’s flex base size (informed by flex-basis, or the item’s natural content/width if flex-basis is auto), then it computes how much free space remains in the container along the main axis, and finally it distributes that free space (or negative space, if items overflow) among items according to their flex-grow and flex-shrink factors. This is fundamentally different from block layout, where each element is sized independently without regard to its siblings. In Flexbox, items negotiate space collectively as a group, which is exactly what makes it so good at building rows of cards, navigation bars, and toolbars that need to adapt to available width.

It’s important to note that display: flex only affects an element’s direct children. Grandchildren are unaffected and continue to use normal block/inline layout unless you also make them flex containers.

Syntax

Flexbox properties split into two groups: container properties (applied to the parent with display: flex) and item properties (applied to the direct children).

Property Applies to Purpose
display Container Set to flex or inline-flex to activate Flexbox
flex-direction Container Sets the main axis: row, row-reverse, column, column-reverse
flex-wrap Container Allows items to wrap onto multiple lines: nowrap (default), wrap, wrap-reverse
justify-content Container Aligns items along the main axis: flex-start, flex-end, center, space-between, space-around, space-evenly
align-items Container Aligns items along the cross axis on a single line: stretch (default), flex-start, flex-end, center, baseline
align-content Container Aligns multiple wrapped lines along the cross axis (needs flex-wrap: wrap)
gap Container Sets spacing between items, both row and column gaps
flex-grow Item A unitless number describing how much of the extra free space this item should absorb
flex-shrink Item A unitless number describing how much this item should shrink when space is insufficient
flex-basis Item The item’s starting size before growing/shrinking is applied (a length, percentage, or auto)
flex Item Shorthand for flex-grow flex-shrink flex-basis, e.g. flex: 1 1 auto
align-self Item Overrides align-items for a single item

Examples

Example 1: A basic flex row

Given three sibling elements inside a container — for example <div class="container"><div class="box"></div><div class="box"></div><div class="box"></div></div> — the following CSS lays them out in a row with even spacing:

.container {
  display: flex;
  justify-content: space-between;
  background-color: #f0f0f0;
  padding: 20px;
}

.box {
  width: 100px;
  height: 100px;
  background-color: #4a90d9;
}

Result: Three 100px blue squares appear in a single horizontal row inside a light-gray padded container. The first square sits flush against the left edge of the container’s content box, the last sits flush against the right edge, and the middle square is positioned so the gaps on either side of it are equal — this is exactly what space-between does: it puts all the leftover horizontal space between the items, none before the first or after the last.

Example 2: Wrapping items with gap

.container {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 12px;
  width: 300px;
}

.box {
  width: 120px;
  height: 80px;
  background-color: #e07a5f;
}

Result: If this container holds four .box elements, only two fit per row (2 × 120px plus the 12px gap comes close to the 300px width, and the third box no longer fits). Because flex-wrap is set to wrap, the third and fourth boxes drop down onto a second line rather than shrinking or overflowing, producing a two-column grid-like arrangement of orange boxes with consistent 12px gaps between them both horizontally and vertically.

Example 3: A realistic navigation bar

.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 24px;
  background-color: #1f2937;
}

.logo {
  color: #ffffff;
  font-weight: bold;
  flex: 0 0 auto;
}

.nav-links {
  display: flex;
  gap: 20px;
  flex: 1 1 auto;
  justify-content: flex-end;
}

.nav-links a {
  color: #d1d5db;
  text-decoration: none;
}

Result: A dark slate-colored bar spans the width of its parent. Inside it, the bold white logo text sits pinned to the left edge and vertically centered thanks to align-items: center on .navbar. The .nav-links element (itself a nested flex container) grows to fill the remaining horizontal space and pushes its light-gray link text to the far right, with 20px of gap between each link. This is the standard pattern for building a responsive header: an outer flex row for overall placement, with flex: 0 0 auto keeping the logo at its natural size while flex: 1 1 auto lets the nav links section absorb the rest of the space.

How it works step by step / Under the hood

When the rendering engine lays out a flex container, it performs roughly these steps for every layout pass:

  • 1. Establish the axes. Based on flex-direction, decide which physical direction is the main axis and which is the cross axis.
  • 2. Compute each item’s flex base size. If flex-basis is a length, that’s the base size. If it’s auto, the base size falls back to the item’s own width (in a row container) or height (in a column container), or its content size if none is set.
  • 3. Sum the base sizes and compare to the container’s available main-axis space. This produces either positive free space (items are smaller than the container) or a deficit (items are larger than the container).
  • 4. Distribute the difference. If there is positive free space, it is divided among items in proportion to their flex-grow values — an item with flex-grow: 2 gains twice as much extra space as one with flex-grow: 1; items with flex-grow: 0 get none. If there is a deficit, it is removed from items in proportion to their flex-shrink values (also weighted by their base size), so larger items shrink more than smaller ones by default.
  • 5. Position items along the main axis according to justify-content, inserting any resulting gaps or start/end offsets.
  • 6. Stretch or align items along the cross axis according to align-items (or an item’s own align-self) — the default, stretch, is why flex items often end up the same height as their tallest sibling even without an explicit height being set.
  • 7. If wrapping is enabled and items don’t fit on one line, the engine breaks them onto additional flex lines and then applies align-content to position those lines relative to one another on the cross axis.

Knowing this order explains a lot of Flexbox behavior that otherwise seems surprising — for instance, why a flex item with a fixed width can still shrink (because flex-shrink defaults to 1, so the width is only a starting point, not a hard floor, unless you also set flex-shrink: 0 or a min-width).

Common Mistakes

Mistake 1: Confusing the flex-grow longhand with the flex shorthand. A very common typo is trying to pass three values to flex-grow, which only accepts a single unitless number:

.item {
  flex-grow: 1 1 auto;
}

This is wrong because flex-grow is a longhand property that only controls growth — it has no concept of a shrink factor or a basis. What was actually intended is the flex shorthand, which combines all three:

.item {
  flex: 1 1 auto;
}

Mistake 2: Reaching for justify-content to center something vertically. Because justify-content is the property most people learn first, it’s tempting to use it for any kind of centering:

.container {
  display: flex;
  justify-content: center;
}

In a default row-direction container, this only centers items horizontally along the main axis — it has no effect on vertical position. To center items vertically (on the cross axis), use align-items instead:

.container {
  display: flex;
  align-items: center;
}

To center in both directions at once, combine justify-content: center and align-items: center on the same container.

Best Practices

  • Reach for Flexbox for one-dimensional layouts (a single row or column); use Grid when you need to control rows and columns together.
  • Prefer the flex shorthand (flex: 1 1 0, flex: 0 0 auto, etc.) over setting flex-grow, flex-shrink, and flex-basis separately — it’s harder to leave a property at an unintended default.
  • Use gap instead of margins on flex items for spacing between them; it avoids extra margin on the first/last item and works identically whether or not items wrap.
  • Remember display: flex only makes direct children into flex items — nested elements need their own display: flex if you want the same behavior recursively.
  • Set flex-shrink: 0 (or use flex: none) on items that must never shrink below their natural size, such as icons or fixed-width sidebars.
  • When building responsive components, combine flex-wrap: wrap with a sensible flex-basis (e.g. flex-basis: 200px) so items wrap into a new line gracefully instead of squeezing indefinitely.

Practice Exercises

Exercise 1: Create a flex container with four child boxes. Make the second box twice as wide as the others once extra space is distributed, without setting an explicit pixel width on any box. (Hint: think about flex-grow ratios.)

Exercise 2: Build a card row that wraps onto a new line on narrow containers, with 16px of space between cards both horizontally and vertically, and each card having a minimum width of 220px.

Exercise 3: Build a simple toolbar where a title sits on the left, a search box in the middle grows to fill available space, and two fixed-size icon buttons sit on the right — all vertically centered.

Summary

  • display: flex turns an element into a flex container and its direct children into flex items.
  • flex-direction sets the main axis; justify-content aligns along it, while align-items/align-content align along the cross axis.
  • Item sizing is resolved through flex-basis, flex-grow, and flex-shrink, usually written together as the flex shorthand.
  • flex-wrap lets items flow onto multiple lines instead of shrinking or overflowing.
  • Flexbox is one-dimensional layout — use it for rows or columns of content, and reach for Grid when you need two-dimensional control.