Four layers
Every element is a rectangle made of four nested areas: the content, then padding around it, then the border, then the margin outside that.
- Padding is inside the border, so the background reaches into it.
- Border is drawn between padding and margin.
- Margin is outside everything and is always transparent — a background never shows there.
┌─────────────── margin ────────────────┐
│ ┌──────────── border ─────────────┐ │
│ │ ┌───────── padding ─────────┐ │ │
│ │ │ │ │ │
│ │ │ content │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└───────────────────────────────────────┘
box-sizing decides what width means
By default, width sets the content width and padding and border are added on top. Set width: 300px; padding: 20px; border: 1px and the element occupies 342 pixels. That is content-box, and it is the reason the first line of almost every stylesheet is a reset.
*, *::before, *::after {
box-sizing: border-box;
}
| Value | width: 300px; padding: 20px; border: 1px |
|---|---|
content-box (default) | Content 300, total 342. |
border-box | Total 300, content 258. |
It matches how people think: "this box is 300 wide". It also makes percentage widths work with padding, which content-box makes painful.
The shorthands
padding: 16px; /* all four */
padding: 16px 24px; /* block (top/bottom), inline (left/right) */
padding: 16px 24px 8px; /* top, inline, bottom */
padding: 16px 24px 8px 12px; /* top, right, bottom, left */
/* logical versions, which flip in right-to-left layouts */
padding-block: 16px;
padding-inline: 24px;
padding-block-start: 16px;
Margins collapse
Two vertical margins that meet do not add up — the larger one wins and the smaller disappears. A 30px margin-bottom meeting a 20px margin-top produces a 30px gap, not 50.
This is deliberate: it is what keeps paragraphs evenly spaced whatever their individual margins say. It happens only vertically, and never in flex or grid containers.
/* 1. between siblings — 30px, not 50 */
h2 { margin-bottom: 30px; }
p { margin-top: 20px; }
/* 2. a parent and its first child: the child's margin escapes the parent */
.card { margin-top: 0; }
.card h3 { margin-top: 24px; } /* pushes .card down, not h3 */
/* 3. an empty element's own top and bottom margins collapse together */
.card {
display: flow-root; /* exists purely to contain margins and floats */
}
Anything that creates a block formatting context stops collapsing at that boundary: overflow: hidden or auto, display: flow-root, display: flex or grid, position: absolute, a float, or any padding or border on the parent.
Centring with margin
margin: auto centres horizontally in normal flow, and in both directions inside a flex or grid container. It never centres vertically in normal flow.
.wrap {
max-width: 1100px;
margin-inline: auto; /* equal left and right, so it centres */
}
min, max and the sizing keywords
max-width beats width, and min-width beats both. That order is fixed, and it is why width: 100%; max-width: 640px is the standard responsive pattern.
.panel {
width: 100%;
max-width: 640px; /* never wider */
min-height: 12rem; /* never shorter */
}
.label {
width: min-content; /* as narrow as the longest word */
}
.button {
width: max-content; /* as wide as the content on one line */
}
.column {
width: fit-content; /* max-content, capped by the available space */
}
Borders and outlines are different
- A border takes up space and moves everything around it. An outline is drawn on top and takes no space at all.
- That makes
outlinethe right tool for a focus ring: it never causes layout to shift. outline-offsetpushes the ring away from the element, which is often what makes it readable.
:focus-visible {
outline: 2px solid #4c8dff;
outline-offset: 3px;
}
What overflows
Content larger than its box spills out by default — it is visible, it just escapes. overflow: hidden clips it, auto gives a scrollbar when needed, clip clips with no scrolling at all.
A common cause of a page that scrolls sideways is one box slightly wider than the viewport. Find it with document.querySelectorAll("*") and a check on scrollWidth, or by setting outline: 1px solid red on everything.