The Box Model
Date: 2026-09-27
Every element is a rectangle of content, padding, border and margin, and
widthmeans the content box unless you say otherwise. Setbox-sizing: border-boxeverywhere, then learn the three places the model still surprises: margin collapse, percentage padding, and inline boxes.
The box model is how CSS sizes an element: a content area, surrounded by padding, then border, then margin, where each layer adds to the space the element occupies. box-sizing decides which of those layers width and height refer to.
content-box vs border-box
.box { width: 300px; padding: 20px; border: 5px solid; }
content-box (the default) border-box
┌─ border 5 ────────────────────┐ ┌─ border 5 ──────────────┐
│┌─ padding 20 ───────────────┐ │ │┌─ padding 20 ─────────┐ │
││ content 300 │ │ ││ content 250 │ │
│└────────────────────────────┘ │ │└──────────────────────┘ │
└───────────────────────────────┘ └─────────────────────────┘
rendered width = 300+40+10 = 350 rendered width = 300
content-box means adding padding changes the layout. Two 50%-wide columns with any padding overflow their row. border-box makes width mean what a designer means by it, which is why every reset starts with it:
*, *::before, *::after { box-sizing: border-box; }Margin collapse
Vertical margins between blocks don’t add — the larger one wins.
<h2 style="margin-bottom: 32px"> expected gap: 32 + 24 = 56
<p style="margin-top: 24px"> actual gap: 32 ← collapsed
It also happens between a parent and its first or last child. A child’s margin-top pokes out of the parent, and the gap shows up above the parent — the classic “why is my header pushed down” bug.
It only happens in normal block flow. It stops when:
- the parent is a flex or grid container — children’s margins never collapse
- padding or border separates the two margins
- the parent creates a new block formatting context (an independent layout region —
display: flow-root,overflowother thanvisible)
That’s why the bug often “fixes itself” when a layout moves to flex, and why many systems use gap or one-direction margins (only ever margin-block-end) so collapse never comes into play — Spacing Systems.
Percentages resolve against width, even vertically
padding-top: 50% is half the containing block’s width, not its height. That was the basis of the old aspect-ratio hack (padding-top: 56.25% for 16:9). aspect-ratio: 16 / 9 replaces it, and the hack is worth recognising in legacy code rather than writing.
Inline boxes are different
A display: inline element (span, a, em) ignores width and height, and its vertical padding and margin don’t push surrounding lines apart — padding paints, but can overlap the lines above. That’s why an <a> given padding to make a larger tap target looks right and overlaps its neighbours. inline-block or inline-flex gives it a real box.
Failure modes
- Overflow from
content-boxin any codebase missing the reset, or a third-party widget that assumes the other model - Spacing that differs between contexts because margins collapse in block flow and not in flex. The same component gets different gaps in a sidebar and in a grid
- Negative margins to cancel margin collapse, which then break when the parent’s layout mode changes
width: 100%plus margin overflows whateverbox-sizingsays — margin is always outside. A block element in normal flow already fills the width by default, so the fix is usually to deletewidth: 100%
Where it connects
- Flexbox · Grid — size boxes with their own rules on top of this model
- Logical Properties —
margin-block,padding-inline: the same box, named by writing direction