Every element rendered in a browser is a rectangular box, and the CSS box model is the set of rules that determines how big that box actually is. It sounds simple until a developer sets width: 300px on an element, adds padding: 20px and a border: 2px solid, and discovers the element now renders at 344px wide instead of 300px. That gap between the declared width and the rendered width is the box model doing exactly what the specification says it should — which is precisely why understanding it matters more than memorizing it.
The Four Layers of Every Box
The CSS box model defines four concentric layers, from the inside out:
- Content box — where text and child elements actually render, sized by
widthandheight - Padding box — transparent space between the content and the border, set with
padding - Border box — the line drawn around the padding, set with
border - Margin box — transparent space outside the border that separates the element from its neighbors, set with
margin
Each layer adds to the total footprint of the element. Padding and border are visually part of the element (padding often has a background color; border is always visible). Margin is not part of the element at all — it is the space between elements, and unlike padding it never has a background.
Why width Doesn’t Mean What It Looks Like It Means
By default, the CSS box-sizing property is set to content-box. Under content-box, the width and height properties apply only to the content layer. Padding and border are added on top:
.card {
width: 300px;
padding: 20px;
border: 2px solid #ccc;
}
/* Rendered width: 300 + 20 + 20 + 2 + 2 = 344px */
This is the historical default, and it is the source of decades of layout bugs. A developer sets a grid of three 300px cards expecting them to total 900px, adds padding for readability, and the row now overflows its container because the actual rendered width is larger than the declared width.
box-sizing: border-box Fixes the Math
Setting box-sizing: border-box changes what width measures. Under border-box, the declared width includes padding and border — the content area shrinks to accommodate them, rather than the box growing past the declared width:
.card {
box-sizing: border-box;
width: 300px;
padding: 20px;
border: 2px solid #ccc;
}
/* Rendered width: 300px, exactly. Content area shrinks to 254px to make room. */
This is why the near-universal reset rule exists:
*, *::before, *::after {
box-sizing: border-box;
}
Applying border-box globally means every width and height declaration in a project behaves predictably — the number you write is the number you get, regardless of how much padding or border you add later. Most CSS frameworks and starter templates have shipped this reset by default for years, precisely because content-box math is unintuitive for anyone building layouts rather than writing a browser engine.
Margin Collapse: The Box Model’s Strangest Behavior
Adjacent vertical margins between block-level elements do not add together — they collapse to the larger of the two values. If one paragraph has margin-bottom: 20px and the next has margin-top: 30px, the gap between them is 30px, not 50px.
p { margin: 0 0 20px 0; }
p + p { margin-top: 30px; }
/* Gap between paragraphs: 30px (the larger value), not 50px */
Margin collapse only applies to vertical margins between block-level elements in normal flow — it does not apply to horizontal margins, to elements using flexbox or grid layout (their children never collapse margins), or to elements with overflow set to a value other than visible. It also occurs between a parent and its first or last child when there’s no padding, border, or other content separating them — a frequent source of “why is there whitespace above my container” bugs, usually solved with padding-top: 1px on the parent, overflow: hidden, or a display: flow-root declaration on the parent.
Negative Margins Are Legal and Sometimes Useful
Unlike padding and border, margin accepts negative values. A negative margin pulls an element closer to (or overlapping) its neighbors:
.overlap {
margin-top: -10px;
}
This is a deliberate escape hatch, commonly used to pull an element’s edge past its container (a badge overlapping a card corner, a hero image bleeding past its text column) or to counteract unwanted whitespace from margin collapse. It should be used sparingly — reaching for negative margins to fix a layout that fights the box model is often a sign the underlying layout approach (flexbox gap, grid, or a different box-sizing choice) is a better fit than patching over the symptom.
Percentage Values Resolve Against the Containing Block
When padding, margin, or width use percentage values, the percentage resolves against the width of the containing block — including for padding-top and padding-bottom, which is a common source of confusion since vertical properties resolving against a horizontal measurement seems backward. This behavior is intentional and is the mechanism behind the classic “aspect-ratio box” hack: setting padding-top: 56.25% on an empty element produces a box with a 16:9 ratio, because 56.25% of the container’s width becomes the element’s height.
Inline Elements Follow Different Rules
The box model applies differently to inline elements (like <span> and <a> by default) than to block-level elements. Inline elements respect horizontal padding, margin, and border — they push neighboring inline content left and right — but vertical padding, margin, and border do not affect surrounding layout, even though they still render visually. A <span> with padding-top: 20px will visually show that padding, potentially overlapping the line above, without pushing that line away. This is why vertically padding an inline element to build a clickable navigation link often looks wrong until the element is switched to display: inline-block or display: block, both of which restore normal block-level box-model behavior for vertical spacing.
Outline: A Fifth Layer That Doesn’t Count
outline is easy to mistake for a second border, but it behaves differently in one crucial respect: outline does not participate in the box model at all. It draws outside the border box without affecting layout, without consuming space, and without shifting any neighboring content.
.focus-visible-example:focus-visible {
outline: 2px solid #0057b8;
outline-offset: 2px;
}
This is precisely why outline is the right tool for focus indicators: a visible focus ring should never cause a layout shift when an element gains or loses focus, and because outline doesn’t occupy box-model space, toggling it never does. outline-offset adds a gap between the border edge and the outline itself, without needing negative margin or absolute positioning tricks. Some older advice recommends outline: none to remove a “distracting” default focus ring — this is a serious accessibility regression for keyboard users unless a clearly visible replacement focus style is provided, since it removes the only visual indicator of which element currently has keyboard focus.
The Practical Default
For nearly all layout work, the recommended baseline is: apply the universal box-sizing: border-box reset, treat content-box as a legacy default you actively opt out of, use margin for spacing between elements and padding for spacing within an element’s own boundary, and reach for the gap property in grid layouts instead of margin whenever the spacing is between grid items — gap sidesteps margin collapse entirely and is generally the more predictable tool for that specific job.
Frequently Asked Questions
Does box-sizing: border-box affect margin?
No. box-sizing: border-box changes how width and height interact with padding and border only — margin is never included in either box-sizing calculation. Margin is always additional space outside the border box, regardless of which box-sizing value is set. This is why margin is described separately from the box-sizing model rather than as a fourth option within it.
Why does my element have unexpected whitespace above it?
This is usually margin collapse between a parent and its first child. If the parent has no top padding, top border, or other separating content, the child’s margin-top collapses upward and appears to be margin on the parent. Fixing it typically means adding padding-top: 1px, overflow: hidden, or display: flow-root to the parent element.
Should I always use box-sizing: border-box?
For virtually all modern layout work, yes. The universal reset (*, *::before, *::after { box-sizing: border-box; }) is standard practice across essentially every CSS framework and starter template because it makes declared widths behave predictably. There is no meaningful downside to applying it project-wide.
Can padding be negative?
No. Unlike margin, padding (and border-width) cannot be negative — the CSS specification restricts them to zero or positive values. Browsers will ignore or clamp a negative padding declaration. Margin is the only box-model property that legally accepts negative values.
Why doesn’t margin work on inline elements the way I expect?
Vertical margin, padding, and border on inline elements (like <span> or <a> without a display change) render visually but do not affect the layout of surrounding lines — only horizontal spacing pushes neighboring inline content. Switching the element to display: inline-block or display: block restores standard box-model behavior for vertical spacing.
