The Box Model and Spacing
BeginnerEvery element is a box with content, padding, border and margin. Two rules — border-box sizing and single-direction margins — remove most sizing surprises.
Overview
The box model is the first thing everyone learns and the source of the first thing everyone gets wrong: a width of 300px with 20px of padding produces a 340px element, because the default sizing applies width to the content box only. Setting box-sizing: border-box globally makes width mean what people expect. The second recurring surprise is margin collapsing, where adjacent vertical margins merge into one — behaviour that looks like a bug until you know it exists, and that modern gap-based layout mostly avoids.
border-box
The one line every stylesheet starts with, and why.
/* Default: content-box. width applies to the CONTENT only. */
.card { width: 300px; padding: 20px; border: 2px solid }
/* Rendered width = 300 + 40 + 4 = 344px. */
/* border-box: width includes padding and border. */
*, *::before, *::after { box-sizing: border-box }
.card { width: 300px; padding: 20px; border: 2px solid }
/* Rendered width = 300px, with 256px of content. */
/* This is why 'width: 50%' with padding overflows its parent under
the default and behaves under border-box. Set it once, globally,
and never think about it again. */
/* The four layers, outside in:
margin — space OUTSIDE, transparent, can collapse, not in width
border — the visible edge
padding — space INSIDE, takes the background
content — the text or child boxes */
/* Padding vs margin: background reaches into padding, not margin */
.button { padding: .5rem 1rem } /* clickable, coloured */
.button { margin: .5rem 1rem } /* space around, not clickable */Margin Collapsing
Adjacent vertical margins merge. Three cases, and how to stop it.
/* 1. Siblings: the gap is the LARGER margin, not the sum */
.a { margin-bottom: 30px }
.b { margin-top: 20px }
/* gap = 30px, not 50px */
/* 2. Parent and first/last child: the child's margin escapes */
.parent { } /* no padding, no border */
.child { margin-top: 40px } /* pushes the PARENT down 40px */
/* 3. Empty elements collapse their own top and bottom margins */
/* It happens only VERTICALLY, and never inside flex or grid,
or across a border, padding, overflow or a new formatting
context. So any of these stop it: */
.parent { padding-top: 1px }
.parent { display: flow-root } /* the clean, purpose-built fix */
.parent { display: flex }
/* The modern answer is to avoid relying on margins for layout
spacing at all — use gap, which never collapses: */
.stack { display: flex; flex-direction: column; gap: 1rem }
/* And prefer single-direction margins so the source of a gap is
never ambiguous */
.prose > * + * { margin-block-start: 1rem } /* the "owl" */Sizing and Overflow
Constraints beat fixed values, and what to do when content does not fit.
/* Prefer constraints to fixed sizes */
.card { max-width: 60ch } /* readable line length */
.avatar { width: 4rem; aspect-ratio: 1 }
.modal { width: min(90vw, 480px) } /* never wider than the screen */
.hero { min-height: 50vh }
/* Intrinsic sizing keywords */
width: min-content /* as narrow as the longest word allows */
width: max-content /* as wide as the content wants, no wrapping */
width: fit-content /* max-content, capped by the available space */
/* Overflow — the default is to spill visibly */
overflow: visible | hidden | scroll | auto | clip
.table-wrap { overflow-x: auto }
/* A long unbroken string (a URL, a token) is the usual culprit */
.break { overflow-wrap: break-word }
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
.clamp { display: -webkit-box; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; overflow: hidden }
/* overflow: hidden also creates a new formatting context and kills
position: sticky inside it — a very common accidental breakage. */Key Points to Remember
- 1box-sizing: border-box makes width include padding and border, which is what people expect — set it globally
- 2Adjacent vertical margins collapse to the larger value, and a child's margin can escape its parent
- 3Flex and grid gap never collapses, which is why gap-based spacing avoids the whole problem
- 4Prefer constraints — max-width, min(), aspect-ratio — over fixed pixel sizes
- 5overflow: hidden creates a formatting context and silently breaks position: sticky inside it
Interview Questions
Sign in to ask AriaWhat does box-sizing: border-box change?
What is margin collapsing and how do you prevent it?
Why might position: sticky stop working inside a container?
Ask Aria about The Box Model and Spacing
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.