---
title: 'The Flexbox Model'
source: 'https://academia.sh/en/courses/layout-and-responsive-design/flexbox-model'
course: 'Layout Systems and Responsive Design'
language: en
updated: '2026-08-17T18:09:23+00:00'
license: 'CC BY-SA 4.0'
---

# The Flexbox Model

Establishing a flex formatting context, deriving the main and cross axis from the direction declaration, distributing free space on the main axis, and alignment on the cross axis.

The Visual Presentation with CSS course established the rules of normal flow: block boxes
stack on the block axis, inline boxes are placed into line boxes. That course showed
floating and positioning as ways to place boxes side by side; both take a box out of flow
and leave its container unable to compute its height from it.

This lesson covers the **layout model** defined for placing boxes side by side. Flexbox
does not take the path of removing a box from flow; it establishes a new placement rule
inside the container and divides free space among the children.

## The Flex Formatting Context

When `display: flex` is declared on a box, the box **itself** stays a block box in flow;
what changes is the placement rule for its **children**. This box is now a **flex
container**, and its direct children are **flex items**.

Three consequences follow immediately:

1. The children's `display` value is **blockified**. A `span` child stops being an inline
   box and becomes a flex item; it can take a width and a vertical margin.
2. Margins between boxes **do not collapse**. The collapsing rule from the flow-layout
   lesson is only defined for block layout; in a flex formatting context, two adjacent
   margins are added together.
3. Text not wrapped in a tag also becomes a flex item. Since the item has no name, it is
   called an **anonymous box** and cannot be targeted with a selector.

The third point is one of the first places to look when a layout breaks: a line break and
indentation inside the container do not produce whitespace, but a real piece of text does
count as an item.

## Two Axes

Flexbox is a **single-axis** model. Items are laid out along one direction, and free space
is divided along that same direction. This direction is called the **main axis**; the one
perpendicular to it is the **cross axis**.

`flex-direction` determines the main axis:

| Declaration | Main axis | Main start | Cross axis |
|---|---|---|---|
| `row` | inline axis | inline start | block axis |
| `row-reverse` | inline axis | inline end | block axis |
| `column` | block axis | block start | inline axis |
| `column-reverse` | block axis | block end | inline axis |

The naming in the table deliberately does not say "horizontal" and "vertical." The inline
axis is the direction text flows in, and it depends on the document's writing direction; in
a right-to-left document, the first item in the `row` direction starts on the right. Because
flexbox declarations name these directions abstractly, the same style file keeps working
correctly when the direction changes.

The main axis's two ends are called the **main start** and **main end**; the cross axis's
two ends are the **cross start** and **cross end**. Every alignment declaration is read
against these four ends.

## Distributing Free Space

When the items' main-axis sizes are added up and the total is smaller than the container,
**free space** remains. The `justify-content` declaration says where that space goes.

The following program computes six distribution values in a three-item row. The container
is 720 units, the gaps are 16 units.

```js
// axis.mjs — main-axis distribution and cross-axis alignment in a flex row
const CONTAINER = 720;
const GAP = 16;
const boxes = [
  { name: "search", main: 180, cross: 40 },
  { name: "date", main: 140, cross: 32 },
  { name: "status", main: 120, cross: 56 },
];

const totalMain = boxes.reduce((t, b) => t + b.main, 0);
const gapTotal = GAP * (boxes.length - 1);
const freeSpace = CONTAINER - totalMain - gapTotal;
console.log(`container=${CONTAINER}  boxes=${totalMain}  gap=${gapTotal}  free space=${freeSpace}`);

// main-axis distribution: each value places the free space differently
function mainAxis(value) {
  const n = boxes.length;
  let start = 0, extra = 0;
  if (value === "flex-start") { start = 0; extra = 0; }
  if (value === "flex-end") { start = freeSpace; extra = 0; }
  if (value === "center") { start = freeSpace / 2; extra = 0; }
  if (value === "space-between") { start = 0; extra = freeSpace / (n - 1); }
  if (value === "space-around") { extra = freeSpace / n; start = extra / 2; }
  if (value === "space-evenly") { extra = freeSpace / (n + 1); start = extra; }
  const places = [];
  let x = start;
  for (const b of boxes) {
    places.push({ name: b.name, start: x, end: x + b.main });
    x += b.main + GAP + extra;
  }
  return places;
}

console.log("\n--- main axis (justify-content) ---");
for (const v of ["flex-start", "flex-end", "center", "space-between", "space-around", "space-evenly"]) {
  const p = mainAxis(v);
  const text = p.map((b) => `${b.name} ${b.start.toFixed(1)}..${b.end.toFixed(1)}`).join("  ");
  console.log(v.padEnd(14), text);
}

// cross-axis alignment: line height is set by the tallest box
const LINE = Math.max(...boxes.map((b) => b.cross));
console.log(`\n--- cross axis (align-items), line height=${LINE} ---`);
for (const v of ["flex-start", "flex-end", "center", "stretch"]) {
  const p = boxes.map((b) => {
    if (v === "stretch") return `${b.name} 0..${LINE}`;
    const start = v === "flex-start" ? 0 : v === "flex-end" ? LINE - b.cross : (LINE - b.cross) / 2;
    return `${b.name} ${start.toFixed(1)}..${(start + b.cross).toFixed(1)}`;
  });
  console.log(v.padEnd(12), p.join("  "));
}
```

```
container=720  boxes=440  gap=32  free space=248

--- main axis (justify-content) ---
flex-start     search 0.0..180.0  date 196.0..336.0  status 352.0..472.0
flex-end       search 248.0..428.0  date 444.0..584.0  status 600.0..720.0
center         search 124.0..304.0  date 320.0..460.0  status 476.0..596.0
space-between  search 0.0..180.0  date 320.0..460.0  status 600.0..720.0
space-around   search 41.3..221.3  date 320.0..460.0  status 558.7..678.7
space-evenly   search 62.0..242.0  date 320.0..460.0  status 538.0..658.0

--- cross axis (align-items), line height=56 ---
flex-start   search 0.0..40.0  date 0.0..32.0  status 0.0..56.0
flex-end     search 16.0..56.0  date 24.0..56.0  status 0.0..56.0
center       search 8.0..48.0  date 12.0..44.0  status 0.0..56.0
stretch      search 0..56  date 0..56  status 0..56
```

The free space is 248 units, and the six lines place these 248 units in six separate ways.

`flex-start`, `flex-end`, and `center` leave the free space in **one piece**; they only
move its position. The remaining three **split** the free space, and where the shares fall
differs:

- `space-between` gives no share to either end and splits the space evenly between the
  items: $248 / 2 = 124$ units of extra gap.
- `space-around` gives every item its own share, halved between the item's two sides:
  $248 / 3 \approx 82.67$; a half share at the edges $\approx 41.33$, and two half shares
  meeting between items give $\approx 82.67$.
- `space-evenly` equalizes every gap: $248 / 4 = 62$ units, the same at the edge as between.

It is not a coincidence that all three values keep the middle item in the same place: the
distribution is symmetric, and if there is an item in the middle of three, that item stays
centered. The difference is at the edges.

One warning is in order: the notation `start`/`end` is also defined instead of
`flex-start`/`flex-end`, and the two give the same result in most cases. A single style
file settles on one notation; this course uses the `flex-`-prefixed form throughout.

## Alignment on the Cross Axis

There is no free space to distribute on the cross axis; each item is aligned on its own.
`align-items` is written on the container for all items, `align-self` on a single item and
overrides the container's value.

The output's second block shows four values. Line height is set by the tallest item (56
units), and the other two are positioned within that height. `flex-start` pulls to the top,
`flex-end` to the bottom, `center` to the middle.

The fourth value, `stretch`, differs from the others and is the **default**: if an item's
cross-axis size is not declared, it is **stretched** to fill the entire line. This is why
all three items show `0..56` in the output. If an item has a cross-axis size declared,
stretching does not apply; the declared size is kept and the item is aligned to the cross
start.

The practical value of stretching is that boxes standing side by side end up **equal in
height**. Three cards carrying text of different lengths appear the same size with no
height declared at all — in flow layout, this could only be achieved by writing a fixed
height.

A fifth value, `baseline`, aligns items against the baseline of their own first line of
text. It is used when placing headings of different type sizes side by side and text
alignment is wanted instead of top-edge alignment.

## Gap and Automatic Margin

The distance between items can be given in two ways. The first is writing margin on the
items, and this produces an unwanted edge margin on the first and last item. The second is
declaring **gap** on the container: `gap` is applied only **between** items and does not
touch the edges.

This is why the gap total in the calculation above was $16 \times 2 = 32$: three items, two
gaps.

A second tool is writing `auto` as an item's `margin` value. An automatic margin **swallows
the entire** free space on the main axis and runs before `justify-content` is applied. This
is the most direct way to push the last item to the opposite end in a navigation bar:

```css
/* layout.css — step 1: navigation bar on a single axis */
.nav > ul {
  display: flex;
  gap: 1.5rem;
  align-items: center;
}

.nav .station-status {
  margin-inline-start: auto;
}
```

The `margin-inline-start: auto` declaration gives all the free space before the status
badge to that item, and the badge sticks to the end of the row. The same result could also
be reached with `justify-content: space-between`, but that declaration distributes a share
to **every** gap; what is wanted here is a break at a single point.

This file sits next to the `station.css` file established in the Visual Presentation with
CSS course. Visual declarations stay there, layout declarations are collected here; the
reason for the separation is covered in the course's final topic.

## Wrapping

By default, flex items stay on a **single line**; even if their total size exceeds the
container, they do not move to a new line, the items shrink instead. The `flex-wrap: wrap`
declaration changes this behavior, and items that do not fit drop to a new line.

Once wrapping is turned on, a third alignment declaration becomes meaningful:
`align-content` says how the **lines** are distributed on the cross axis, and its set of
values is the same as `justify-content`'s. If there is a single line, this declaration has
no effect.

The distinction here is often confused and can be summarized in one sentence:
`align-items` aligns the **items inside a line**, `align-content` aligns the **lines**
inside the container.

## Summary

- `display: flex` does not change the box itself but the placement rule for its children;
  the children get blockified, their margins do not collapse, and unwrapped text becomes
  an anonymous item.
- Flexbox is single-axis: `flex-direction` determines the main axis, the cross axis is
  perpendicular to it, and both axes are named abstractly relative to writing direction.
- `justify-content` places the free space on the main axis; `flex-start`, `flex-end`, and
  `center` leave the space in one piece, `space-between`, `space-around`, and
  `space-evenly` split it with different sharing rules.
- On the cross axis, the default `stretch` stretches items to the line's height; alignment
  values only become visible once a cross size is declared or stretching is turned off.
- `gap` only applies between items; `margin: auto` swallows the entire free space on the
  main axis and runs before the distribution declaration.

## Next Step

This lesson's calculation took the items' main-axis sizes as fixed, and the free space
always came out positive. In a real layout, sizes come from content, and once the
container narrows, free space drops below zero. In that case, which item shrinks by how
much, and which item does surplus space go to? The next lesson covers the three
declarations that govern this sharing and their arithmetic.
