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:
- The children’s
displayvalue is blockified. Aspanchild stops being an inline box and becomes a flex item; it can take a width and a vertical margin. - 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.
- 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-betweengives no share to either end and splits the space evenly between the items: units of extra gap.space-aroundgives every item its own share, halved between the item’s two sides: ; a half share at the edges , and two half shares meeting between items give .space-evenlyequalizes every gap: 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 : 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: flexdoes 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-directiondetermines the main axis, the cross axis is perpendicular to it, and both axes are named abstractly relative to writing direction. justify-contentplaces the free space on the main axis;flex-start,flex-end, andcenterleave the space in one piece,space-between,space-around, andspace-evenlysplit it with different sharing rules.- On the cross axis, the default
stretchstretches items to the line’s height; alignment values only become visible once a cross size is declared or stretching is turned off. gaponly applies between items;margin: autoswallows 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.