Skip to main content

🧭 Normal Flow and Document Flow

Before you float, position, flex, or grid anything, the browser already has a plan: it lays every element out in a default order called the normal flow. Understanding that default β€” and how to work with it rather than fight it β€” is the quiet secret behind clean, predictable layouts.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Describe normal flow and how block, inline, and inline-block elements are laid out in it
  • Explain line boxes and how line-height and vertical-align control inline layout
  • Predict and control margin collapsing between block elements
  • Connect the box model and box-sizing to how elements take up space
  • Recognise a block formatting context and the four ways it changes layout behaviour

Estimated Time: 40–50 minutes  β€’  Difficulty: Beginner–Intermediate

Hands-on: Demonstrate margin collapsing and then defeat it with a block formatting context.

In This Lesson

What Is Document Flow?

Document flow is the default way a browser arranges elements on the page before any positioning, floating, flexbox, or grid is applied. Content flows top to bottom, and β€” within a line β€” left to right (in left-to-right languages like English). It is the foundation every other layout technique builds on top of, or deliberately breaks out of.

Think of it like a river: content pours in from the top and settles into place naturally. Most of the time you want to work with that current. The whole point of understanding normal flow is knowing when the default already does the job β€” so you don't reach for absolute positioning to solve a problem a single margin would fix.

graph TD A[Document flow] --> B[Normal flow] A --> C[Out of flow] B --> D[Block formatting] B --> E[Inline formatting] C --> F[position: absolute / fixed] C --> G[float] C --> H[flex / grid items]
Normal flow is the layout you get "for free." Every advanced technique in CSS is either a refinement of it or a controlled escape from it β€” so it pays to know exactly what "free" gives you.

Block vs Inline vs Inline-Block

Normal flow is really two cooperating sub-systems: block formatting, where boxes stack vertically, and inline formatting, where content flows horizontally into lines. Which one an element joins depends on its display type.

Behaviourblockinlineinline-block
Starts a new lineYesNoNo
Fills available widthYesNoNo
Respects width/heightYesNoYes
Vertical margin affects layoutYesNoYes
Examplesdiv, p, h1, sectionspan, a, strong, embutton, img*

Block-level elements

They start on a new line, expand to fill their container's width, honour all four margins and paddings, and stack in source order. Constraining a block's width (say, width: 60%) doesn't change its stacking β€” it just makes the box narrower while still owning its own line.

.block-element {
  display: block;
  width: 80%;
  margin: 20px auto;   /* auto left/right margins centre a block */
  padding: 15px;
}

Inline elements

They flow within a line of text, take only the width they need, and ignore width and height. Left and right margins and padding affect layout; top and bottom padding is painted but doesn't push surrounding lines apart.

Inline-block: the hybrid

Flows inline like text, but respects width, height, and all margins like a block. It's the reason old navbars and thumbnail grids worked before flexbox β€” items that sit in a row yet keep real dimensions.

⚠️ The inline-block whitespace gap

Because inline-block elements flow like text, the whitespace between your HTML tags renders as a real space β€” leaving mysterious ~4px gaps between items. Modern layouts avoid this entirely by using flexbox or grid, which ignore inter-element whitespace.

Line Boxes & line-height

To lay out inline content, the browser builds invisible line boxes β€” one per line of text. Each line box is as wide as its container and as tall as the tallest content it holds. When content runs past the edge, a new line box forms below. This is exactly how a paragraph wraps.

Line boxes stacking inside a paragraph A block container holds three stacked line boxes; each spans the full width and holds a line of inline content. paragraph (block container) line box 1 β€” inline content flows across… line box 2 β€” …and wraps when it runs out of room… line box 3 β€” the last, shorter line.
Figure 1 β€” The browser stacks line boxes to hold wrapping inline content. line-height sets each box's minimum height.

The line-height property

line-height sets the minimum height of every line box and is one of the biggest levers on readability. Use unit-less values so children scale proportionally instead of inheriting a fixed computed length:

body {
  line-height: 1.5;   /* unit-less β€” recommended for body text */
}

h1, h2, h3 {
  line-height: 1.2;   /* tighter looks better on large headings */
}

πŸ“– line-height rules of thumb

Body text: 1.4–1.6 reads comfortably.

Headings: 1.1–1.3 keeps big text from feeling loose.

Accessibility: generous line spacing helps readers with dyslexia and low vision β€” WCAG suggests at least 1.5 for body copy.

vertical-align

Within a line box, vertical-align controls how inline (and inline-block) elements line up: baseline (default), middle, text-top, text-bottom, sub, super, or a length. It's how you nudge an icon to sit level with the text beside it β€” note that it only applies to inline-level and table-cell boxes, not block elements.

/* Nudge an inline icon to align with the text it sits beside */
.icon {
  vertical-align: middle;   /* or a small length like -2px for fine tuning */
}

The Box Model & box-sizing

Every element in the flow is a box with four nested layers: content, then padding, then border, then margin. How much room the box claims β€” and therefore how it pushes on the flow β€” depends on all four.

The CSS box model Four nested rectangles labelled margin, border, padding, and content from outermost to innermost. margin border padding content
Figure 2 β€” Content sits inside padding, wrapped by the border, surrounded by margin. Padding and border add to the visible size unless box-sizing says otherwise.

content-box vs border-box

By default (content-box), width sets only the content area β€” padding and border are added on top, so a "200px" box can render 250px wide. With border-box, width includes padding and border, so 200px means 200px on screen. That predictability is why nearly every codebase resets it globally:

/* content-box (default): total width = 200 + 40 + 10 = 250px */
.content-box {
  box-sizing: content-box;
  width: 200px;
  padding: 20px;
  border: 5px solid #333;
}

/* border-box: total width stays 200px; content shrinks to fit */
.border-box {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 5px solid #333;
}

/* The near-universal global reset */
*, *::before, *::after {
  box-sizing: border-box;
}

βœ… Set border-box once, globally

With border-box, an element you set to width: 50% takes exactly half its container no matter how much padding or border you add. It removes a whole category of "why is my box 8px too wide?" bugs.

Margin Collapsing

Here's a flow behaviour that surprises almost every beginner: when the vertical margins of two block elements meet, they don't add together β€” they collapse into a single margin equal to the larger of the two.

.box-a { margin-bottom: 30px; }
.box-b { margin-top: 20px; }
/* The gap between them is 30px, NOT 50px β€” the larger margin wins. */

πŸ“– The rules of margin collapse

Vertical only β€” top and bottom margins collapse; left and right never do.

Block boxes in normal flow only β€” flex and grid items don't collapse.

The larger margin wins (equal margins collapse to that one value).

Parent and first/last child can collapse together if no border, padding, or content separates them β€” a common cause of "why is there space outside my container?"

Preventing margin collapse

When collapse isn't what you want, break the contact between the margins β€” the simplest reliable route is to establish a block formatting context on the parent:

/* Any one of these stops parent/child margin collapse */
.parent { padding-top: 1px; }              /* separation via padding */
.parent { border-top: 1px solid transparent; } /* or a hairline border */
.parent { display: flow-root; }            /* or a new formatting context */
.parent { display: flex; flex-direction: column; } /* flex kills collapse entirely */

Block Formatting Contexts

A block formatting context (BFC) is a self-contained region of the page with its own layout rules. It keeps turning up as the fix for the flow's rough edges, so it's worth naming directly. A BFC does four useful things:

  • Contains floats β€” the parent grows to enclose floated children (this is the float-collapse fix from the previous lesson).
  • Stops margin collapse β€” margins inside a BFC don't collapse with margins outside it.
  • Doesn't overlap floats β€” a BFC sits beside a float instead of flowing under it, creating a clean column.
  • Isolates layout β€” what happens inside stays inside.
/* The explicit, side-effect-free way to make a BFC */
.bfc {
  display: flow-root;
}

/* Other things that happen to create a BFC */
.also-bfc {
  overflow: auto;      /* or hidden */
  /* display: inline-block; */
  /* display: flex; / grid; */
  /* position: absolute; / fixed; */
}

πŸ’‘ One concept, many symptoms

"Container collapsed around a float," "unexpected margin outside my box," "text won't form a clean column beside a float" β€” these look like three different bugs, but establishing a BFC (usually with display: flow-root) fixes all three. Learn the concept once and you've learned the fix for a whole family of flow problems.

Breaking out of the flow

Finally, remember the ways to leave normal flow entirely β€” each is its own tool covered elsewhere in this module:

  • Positioning β€” absolute, fixed, and stuck sticky elements leave the flow.
  • Floating β€” partially leaves the flow while letting content wrap.
  • Flex & grid β€” a flex or grid container lays out its children by its own rules.
  • display: none β€” removes the element completely, reserving no space.

Hands-on Exercise

πŸ‹οΈ See Margin Collapse β€” Then Defeat It

Objective: Observe margin collapsing first-hand and fix it with a block formatting context.

Instructions:

  1. Create a .parent with no padding or border, containing one .child that has margin-top: 40px.
  2. Give the parent a visible background. Notice the child's margin escapes outside the parent β€” the parent doesn't move down, the whole thing does.
  3. Now add display: flow-root to the parent. Observe the 40px gap move inside, pushing the child down within the parent as expected.
  4. As a bonus, add two stacked children with margin-bottom: 30px and margin-top: 20px and confirm the gap between them is 30px, not 50px.
πŸ’‘ Hint

The escaping-margin effect is parent/child margin collapse. Anything that establishes a BFC on the parent stops it: a top padding, a top border, or the cleanest option, display: flow-root.

βœ… Sample solution
<div class="parent">
  <div class="child">I have margin-top: 40px</div>
</div>
.parent {
  background: #e0e7ff;
  /* Before: margin of .child escapes and moves the whole parent.   */
  /* After:  flow-root contains it, so the 40px sits inside.        */
  display: flow-root;
}
.child {
  margin-top: 40px;
  background: #6366f1;
  color: #fff;
  padding: 10px;
}

Best Practices

βœ… Do

  • Let normal flow do the work β€” reach for positioning or float only when the default can't express your intent.
  • Set box-sizing: border-box globally for predictable widths.
  • Use unit-less line-height so it scales with font size down the tree.
  • Reach for display: flow-root when you need a clean, side-effect-free BFC.

⚠️ Don't

  • Don't be blindsided by margin collapse β€” expect the larger of two touching vertical margins, not their sum.
  • Don't expect width/height or vertical margins to move a plain inline element.
  • Don't forget that inline-block introduces whitespace gaps from your HTML source formatting.
  • Don't use overflow: hidden just to make a BFC when content legitimately needs to overflow.

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Normal flow is the browser's default layout: block boxes stack, inline content flows into line boxes.
  • Inline elements ignore width/height and vertical margins; inline-block honours them but adds whitespace gaps.
  • line-height (unit-less) sets line-box height; vertical-align aligns inline content within it.
  • The box model plus box-sizing: border-box determines how much space a box claims.
  • Vertical margins collapse to the larger value; a block formatting context (display: flow-root) stops that and contains floats.

🎯 Quick Quiz

Question 1: One box has margin-bottom: 40px, the next has margin-top: 25px. How large is the gap between them?

Question 2: With box-sizing: border-box, what does width: 200px; padding: 20px; border: 5px render as on screen?

Question 3: Which single declaration cleanly makes a container both contain its floats and stop margin collapse, with no side effects?

πŸ“š Further Reading

πŸš€ What's Next?

With the flow, the box model, and formatting contexts under your belt, we move on to making things look polished: shadows and visual effects β€” box-shadow, text-shadow, gradients, and more.

πŸŽ‰ Great work!

You now understand the default the browser starts from β€” which means every layout choice you make from here is deliberate, not accidental.