Skip to content
academia.sh

Lesson 01 / 23

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.

Contents

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.

// 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=124248 / 2 = 124 units of extra gap.
  • space-around gives every item its own share, halved between the item’s two sides: 248/382.67248 / 3 \approx 82.67; a half share at the edges 41.33\approx 41.33, and two half shares meeting between items give 82.67\approx 82.67.
  • space-evenly equalizes every gap: 248/4=62248 / 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×2=3216 \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:

/* 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.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close