π§ 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-heightandvertical-aligncontrol inline layout - Predict and control margin collapsing between block elements
- Connect the box model and
box-sizingto 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.
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.
| Behaviour | block | inline | inline-block |
|---|---|---|---|
| Starts a new line | Yes | No | No |
| Fills available width | Yes | No | No |
| Respects width/height | Yes | No | Yes |
| Vertical margin affects layout | Yes | No | Yes |
| Examples | div, p, h1, section | span, a, strong, em | button, 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-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.
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 stuckstickyelements 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:
- Create a
.parentwith no padding or border, containing one.childthat hasmargin-top: 40px. - 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.
- Now add
display: flow-rootto the parent. Observe the 40px gap move inside, pushing the child down within the parent as expected. - As a bonus, add two stacked children with
margin-bottom: 30pxandmargin-top: 20pxand 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-boxglobally for predictable widths. - Use unit-less
line-heightso it scales with font size down the tree. - Reach for
display: flow-rootwhen 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/heightor vertical margins to move a plaininlineelement. - Don't forget that inline-block introduces whitespace gaps from your HTML source formatting.
- Don't use
overflow: hiddenjust 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-alignaligns inline content within it. - The box model plus
box-sizing: border-boxdetermines 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.