Lesson 12 / 26
Flow Layout
Normal flow's block and inline placement rules; the three cases of margin collapsing and its numeric result.
Contents
The previous lesson defined the type of boxes: a block box occupies its own line, an inline box stays in the flow of the text. The word “flow” was used there without being defined. This lesson defines it.
Normal flow is the way boxes are placed when no positioning declaration is written. This is the default for every layout model, and taking a box out of flow is something that must be declared separately. Knowing the rules of flow lets most layout problems be solved with fewer declarations than expected.
Block Placement
Block boxes inside a block box stack one after another along the block axis. The rules are three items:
- Each block box’s size on the inline axis, when not declared, fills the containing box’s content box.
- Its size on the block axis, when not declared, is determined by its content.
- The next box starts after the previous one’s margin edge.
The third rule is written incompletely. Two boxes’ facing margins are not added together; they collapse.
Margin Collapsing
Margin collapsing is the reduction of two margins on the block axis into a single margin. The rule is this:
- If both values are positive, the result is the larger of the two.
- If both values are negative, the result is the smaller of the two (the one with the larger absolute value).
- If one is positive and one is negative, the result is the sum of the two.
The program below applies this rule and computes the placement of three paragraphs in flow.
// collapse.mjs — applies the margin collapsing rules // rule: the result of margins that collapse together // if all are positive, the largest // if all are negative, the smallest (largest in absolute value) // if mixed, largest positive + smallest negative function collapse(...margins) { const pos = margins.filter((m) => m > 0); const neg = margins.filter((m) => m < 0); const maxPos = pos.length ? Math.max(...pos) : 0; const minNeg = neg.length ? Math.min(...neg) : 0; return maxPos + minNeg; } console.log("--- between two siblings ---"); const cases = [[32, 16], [16, 32], [24, 24], [32, -8], [-8, -16], [0, 24]]; for (const [bottom, top] of cases) { console.log(`bottom=${String(bottom).padStart(3)} top=${String(top).padStart(3)} -> gap=${String(collapse(bottom, top)).padStart(3)} (if summed ${bottom + top})`); } console.log("\n--- parent and first child (escapes outward if no border/padding) ---"); function parentChild({ parentMargin, childMargin, parentPadding, parentBorder }) { if (parentPadding > 0 || parentBorder > 0) { return { outside: parentMargin, inside: childMargin, collapses: false }; } return { outside: collapse(parentMargin, childMargin), inside: 0, collapses: true }; } for (const c of [ { parentMargin: 16, childMargin: 32, parentPadding: 0, parentBorder: 0 }, { parentMargin: 16, childMargin: 32, parentPadding: 1, parentBorder: 0 }, { parentMargin: 16, childMargin: 32, parentPadding: 0, parentBorder: 1 }, ]) { const r = parentChild(c); console.log(`parentPadding=${c.parentPadding} parentBorder=${c.parentBorder} -> outside=${r.outside} inside=${r.inside} collapses=${r.collapses}`); } console.log("\n--- an empty box collapses within itself ---"); console.log(`top=20 bottom=12, no content -> box's total vertical space = ${collapse(20, 12)}`); console.log("\n--- three paragraphs in flow ---"); const paragraphs = [ { name: "p1", marginTop: 0, height: 24, marginBottom: 16 }, { name: "p2", marginTop: 16, height: 48, marginBottom: 16 }, { name: "p3", marginTop: 32, height: 24, marginBottom: 0 }, ]; let y = 0; for (let i = 0; i < paragraphs.length; i++) { const gap = i === 0 ? paragraphs[i].marginTop : collapse(paragraphs[i - 1].marginBottom, paragraphs[i].marginTop); y += gap; console.log(`${paragraphs[i].name}: gap=${String(gap).padStart(2)} top=${String(y).padStart(3)} bottom=${y + paragraphs[i].height}`); y += paragraphs[i].height; } console.log(`total height: ${y}`);
--- between two siblings --- bottom= 32 top= 16 -> gap= 32 (if summed 48) bottom= 16 top= 32 -> gap= 32 (if summed 48) bottom= 24 top= 24 -> gap= 24 (if summed 48) bottom= 32 top= -8 -> gap= 24 (if summed 24) bottom= -8 top=-16 -> gap=-16 (if summed -24) bottom= 0 top= 24 -> gap= 24 (if summed 24) --- parent and first child (escapes outward if no border/padding) --- parentPadding=0 parentBorder=0 -> outside=32 inside=0 collapses=true parentPadding=1 parentBorder=0 -> outside=16 inside=32 collapses=false parentPadding=0 parentBorder=1 -> outside=16 inside=32 collapses=false --- an empty box collapses within itself --- top=20 bottom=12, no content -> box's total vertical space = 20 --- three paragraphs in flow --- p1: gap= 0 top= 0 bottom=24 p2: gap=16 top= 40 bottom=88 p3: gap=32 top=120 bottom=144 total height: 144
The first block shows the third rule most clearly: when two neighbors each give 24 units of margin, the distance between them comes out not 48 but 24. If they were summed, giving every paragraph the same margin would double the gap between paragraphs.
This is the reason for collapsing: in a text document, every block says “leave this much space before and after me,” and neighboring requests should not double each other. The collapsing rule lets each block declare its own request independently while the result stays the larger of the two.
Three Cases of Collapsing
Collapsing does not happen only between siblings.
Between siblings. A box’s bottom margin and the next one’s top margin collapse. The output’s first block shows this.
Between a parent and its first/last child. A box’s top margin and its first child’s top margin collapse and escape outward. The output’s second block gives this: the 16- and 32-unit margins merged and came out as 32 outside the box, leaving zero inside. The same thing happens at the bottom with the last child.
This behavior is often surprising: when a section is given a background, the child’s top margin appears not inside the colored area but outside it. The same block’s next two lines show the fix too: once a single unit of padding or border is added to the parent, collapsing stops and the margin stays inside.
An empty box collapses within itself. A box with no content, height, padding, or border has its own top and bottom margins collapse with each other. In the output’s third block, the 20- and 12-unit margins came down to a single 20 units.
When Collapsing Does Not Happen
Collapsing is not universal. It does not happen in these cases:
- Never on the inline axis. Two boxes sitting side by side always have their horizontal margins summed. Collapsing is defined only on the block axis.
- When there is border or padding between the boxes.
- When a box is taken out of flow: absolutely positioned or floated boxes take no part in collapsing.
- When a box is an independent formatting context:
display: flow-root, boxes whoseoverflowvalue is other thanvisible, and children inside a flexible box or grid layout do not collapse.
The last item is the one used most in practice. Gaps given with gap in a layout model do not
collapse; the declared value applies as-is. This makes gaps predictable, and explains why the
next course’s layout systems eliminate the collapsing problem.
Inline Placement
Inline boxes do not stack on the block axis; they are placed into line boxes. A line box fills the containing block’s inline axis and takes as many inline boxes as fit inside it. Whatever does not fit moves to the next line box.
A line box’s height is computed from the heights of the boxes inside it and their
line-height values. The detail of this computation is taken up in the typography lesson;
what matters here are its two consequences.
First, giving an inline box vertical margin does not grow the line box — this is the reason for the behavior seen in the previous lesson’s table.
Second, what determines the distance between line boxes is not margin but line-height.
Line spacing within a paragraph therefore cannot be adjusted with margin.
The Practical Result of Flow
The rules of normal flow ensure that a document is readable with no layout declaration at all. Blocks stack, text breaks into lines, boxes fill their containers. The station document written in the Web Fundamentals and HTML course was readable from start to finish even without a single line of CSS.
The writing discipline that follows from this is: a layout problem is first attempted with the rules of flow. Declarations that take boxes out of flow — floating and positioning — are written when flow cannot give something needed, not because flow is unknown.
/* station.css — step 11: flow-based spacing */ main > * + * { margin-block-start: 1rem; } main > section { margin-block-start: 2.5rem; } .masthead > :first-child { margin-block-start: 0; }
The first rule puts a gap between every pair of siblings in the main content and does not touch the first element — the natural result of the sibling combinator. The second rule widens the gap between sections and, thanks to collapsing, is not added to the first: 1rem and 2.5rem collapse, and the result is 2.5rem. The third rule zeroes out the masthead’s first child’s margin instead of letting it escape outward.
Summary
- In normal flow, block boxes stack on the block axis, inline boxes are placed into line boxes; whatever does not fit moves to the next line box.
- Two neighboring margins on the block axis are not summed, they collapse: if both are positive, the larger applies; if both are negative, the smaller; if mixed, the sum applies.
- Collapsing happens in three cases: between siblings, between a parent and its first/last child, and within an empty box. In the second case, the margin escapes outward past the parent box.
- Collapsing never happens on the inline axis; it stops when border or padding sits in between, when the box is taken out of flow, or when it is an independent formatting context.
- The distance between line boxes is set by
line-height, not margin; vertical margin given to an inline box does not change the line height.
Next Step
Flow lays boxes out in one direction: blocks stacked, inline boxes side by side. Wanting an image to sit next to text and have the text flow around it fits neither pattern. The next lesson takes up floating, which was defined for exactly this need, and the height problem it brings with it.
To keep your progress and take notes, Log in
My notes
Log in to take notes.