---
title: 'Flow Layout'
source: 'https://academia.sh/en/courses/css-fundamentals/flow-layout'
course: 'Visual Presentation with CSS'
language: en
updated: '2026-08-17T18:09:15+00:00'
license: 'CC BY-SA 4.0'
---

# Flow Layout

Normal flow's block and inline placement rules; the three cases of margin collapsing and its numeric result.

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:

1. Each block box's size on the inline axis, when not declared, fills the containing box's
   content box.
2. Its size on the block axis, when not declared, is determined by its content.
3. 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.

```js
// 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 whose
  `overflow` value is other than `visible`, 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.

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