A div in normal flow has no padding or border, and its first child has margin-top: 40px. The 40px of space appears above the parent rather than inside it. Why does CSS do that, and where does the parent's box actually start?
answer
- nothing between the two top edges
- the margin ends up outside the parent
- padding or border breaks the adjacency
- height: auto lets the bottom one escape too
- flow-root is the surgical fix
basics
~20 sThe child's top margin collapses with the parent's top margin, producing one margin outside the parent. The parent's top border edge ends up flush with the child's top edge, so the space is pushed above the parent instead of inside it.
solid answer
~40 sThis is parent/first-child margin collapsing. A parent's top margin and its first in-flow child's top margin are adjoining when nothing separates them — no `border-top`, no `padding-top`, no inline content, no clearance — so they collapse into a single margin that sits *outside* the parent. The parent is pushed down 40px and its top border edge lands exactly on the child's top edge, which is why the parent's background starts below the gap rather than behind it. The mirror rule applies at the bottom: the parent's bottom margin collapses with its last in-flow child's bottom margin when the parent's height is auto and there is no bottom padding or border. Any of a border, padding, `display: flow-root`, or making the parent a flex or grid container keeps the margin inside.
code
html · 11 lines<style>
.card { background: #dde; }
.card > h2 { margin-top: 40px; }
.fixed { display: flow-root; }
</style>
<div class="card">
<h2>gap is ABOVE the blue box</h2>
</div>
<div class="card fixed">
<h2>gap is INSIDE the blue box</h2>
</div>go deeper
Recognise the symptom: a mysterious gap above a container whose own styles declare no margin usually comes from its first child's top margin.
Explain the adjacency conditions precisely — no border-top, no padding-top, no preceding line box, no clearance — and state that the collapsed margin ends up outside the parent.
Diagnose it in a real tree where the margin escaped several wrappers, and choose a fix by its side effects rather than reaching for overflow: hidden by reflex.
Own the convention that avoids the class of bug entirely — container-owned spacing via gap, or one-direction margins — and be able to justify the migration cost of imposing it.
## The symptom You add `margin-top` to a heading inside a card, and instead of pushing the heading down inside the card, the whole card moves down and the heading sits flush against its top edge. Give the card a background colour and the gap is clearly outside it. Nothing in your stylesheet declares a margin on the card, yet the card has one. ## The mechanism CSS treats a parent's top margin and its first in-flow child's top margin as **adjoining** when nothing lies between the parent's top border edge and the child's top border edge. "Nothing" is a precise list: - no `border-top` on the parent, - no `padding-top` on the parent, - no inline content (a line box) before the child, - no clearance on the child, - and the parent does not establish a new block formatting context. When all of that holds, the two margins collapse into one margin, and — this is the part that surprises people — the resulting margin belongs to the *outside* of the parent. The parent's border box therefore begins exactly where the child's border box begins. ```css .card { background: #eef; } /* no padding, no border */ .card > h2 { margin-top: 40px; } ``` The used result is a 40px margin above `.card`, and zero space between `.card`'s top edge and the heading. ## The same rule at the bottom A parent's bottom margin collapses with its last in-flow child's bottom margin under the mirrored conditions: no `padding-bottom`, no `border-bottom`, no content after the child, and the parent's height must be `auto` (with `min-height` of zero). A fixed `height` gives the child's bottom margin something to sit against, so the collapse does not happen. This is why the bug is often asymmetric — a card with `height: 200px` leaks its top margin but not its bottom one. ## Chains of collapse More than two margins can collapse at once. If a section contains a div which contains a heading, and none of the wrappers has padding, a border, or a preceding line box, then all three top margins are adjoining and merge into one margin outside the outermost box. This is why the escaped gap can appear several levels above where you declared it, and why hunting for the declaration in DevTools can be confusing: the element that *shows* the gap is not the element that *declared* it. ## Fixes, in order of least surprise - **`display: flow-root` on the parent.** Purpose-built: it makes the parent establish a block formatting context, which stops its margins collapsing with its in-flow children, and it adds no size, no clipping and no change to how the children lay out. - **`padding-top: 1px` or a transparent `border-top`.** Effective — they physically separate the two margins — but they change the box's metrics, and 1px of padding is a hack a reviewer will ask about. - **`overflow: hidden` (or `auto`).** Also establishes a formatting context, but it drags in real side effects: the box becomes a scroll container and can clip shadows, focus rings or dropdowns. - **`display: flex` / `display: grid` on the parent.** Margins of flex and grid items never collapse, so this fixes it, but it also changes how the children are laid out — a large change to buy one small behaviour. - **Do not** set `margin-top: 0` on the parent expecting the gap to go away. The parent's own margin was probably already zero; the gap comes from the child's margin, and zero simply loses the collapse. ## Why this design at all Block flow was designed for documents, where the collapse produces sane defaults: a section wrapper around paragraphs does not introduce doubled spacing, and the vertical rhythm of headings and paragraphs comes out even regardless of how deeply the markup nests them. In component-oriented layouts the same behaviour reads as a leak, which is why modern component CSS tends to put spacing on a `gap` in a flex or grid container, or to use one-direction margins, rather than relying on block flow's arithmetic.
- Why does adding padding-top: 1px to the parent stop the child's margin escaping?Because the two margins are only adjoining when nothing lies between the parent's top border edge and the child's top border edge. Padding is exactly such a thing: it physically separates them, so they are no longer adjoining and cannot collapse. The child's 40px margin then sits inside the parent, below that 1px.
- Does the same thing happen with the parent's bottom margin?Yes, under mirrored conditions: no bottom padding or border, nothing after the last child, and the parent's height resolved as auto with a zero min-height. Give the parent a fixed height and the bottom collapse stops, which is why the bug often looks asymmetric — top margin leaks, bottom margin does not.
- Why is display: flow-root usually a better fix than overflow: hidden?Both make the parent establish a block formatting context, which is what stops the collapse, but `overflow: hidden` also turns the box into a scroll container and clips anything that overflows — shadows, focus rings, dropdowns. `flow-root` was added to express the formatting-context intent alone, with no clipping and no other side effects.
saying these in an interview costs you the question
- Says the parent's margin simply overrides the child's
- Thinks the child's margin is being ignored by the browser
- Sets margin-top: 0 on the parent and expects the gap to vanish
- Believes only the first child can leak, never the last
- Reaches for position: relative as the fix