Why 300px Is Not 300px
In one line
width: 300px is, in the default settings, the width of the content area, not the width of the box. And vertical margins, instead of being added, are merged. These two account for most layout mishaps.
Why this was needed
"I placed two columns at 50% each, but the line wraps." "I added padding and the card sticks out of the grid." "I gave the element above margin-bottom: 20px and the element below margin-top: 30px, but the gap is 30px, not 50px."
All three happen not from not knowing CSS but from putting in values without knowing the model. And for all three, all you can come up with by eye is "why is this happening." If you know the calculation rules, you get the answer on the spot.
How it works — the four layers of a box
┌─────────── margin ───────────┐
│ ┌───────── border ─────────┐ │
│ │ ┌─────── padding ──────┐ │ │
│ │ │ content │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
These are the four layers defined by the CSS 2 specification's box model. With the default box-sizing: content-box, width refers only to the innermost content. So
.card { width: 300px; padding: 20px; border: 1px solid; }
The horizontal space this card takes up is 300 + 20 + 20 + 1 + 1 = 342px. If the parent is 300px, it sticks out.
If you give it box-sizing: border-box, width becomes the value including the border. The card above becomes exactly 300px, and the content area shrinks to 258px. MDN's box-sizing documentation compares the two modes.
That is why almost every project puts these three lines at the very top.
*, *::before, *::after { box-sizing: border-box; }
margin is not included in width in either mode. This is the one thing with no exceptions.
Margin collapsing — merged, not added
Vertical margins that touch each other are not added; they become the larger of the two values. MDN's margin collapsing documentation sums up three cases.
- Adjacent siblings — the bottom margin of the upper element and the top margin of the lower element merge
- A parent and its first/last child — if there is no border or padding between them, the child's margin leaks out of the parent
- An empty element — its own top and bottom margins merge with each other
The second is the one that most often confuses people. You gave the child margin-top to put space inside the parent box, but the whole parent gets pushed down. If there is a background color, that color doesn't follow along, which looks even stranger.
The way to stop collapsing is to put something in between. One line of padding-top on the parent, one line of border-top, or display: flow-root. If you create a new formatting context (BFC), the margin is trapped inside.
And horizontal margins never collapse. Left and right are always added.
There is no collapsing inside flex and grid
Margin collapsing is a rule of normal flow (the block formatting context). It does not happen between flex items or between grid items.
That is why you meet collapsing less often in today's layouts. Instead, gap takes its place. gap goes in only between items and not on the outside, so the whole rule of "removing the margin from only the last element" becomes unnecessary.
.list { display: flex; flex-direction: column; gap: 16px; }
This one line replaces .list > * + * { margin-top: 16px }.
What values are measured against
Percentages are based on the width of the containing block. There is a trap here — percentages for padding-top and margin-top are also measured against the width. Not the height. The old technique of using padding-top: 56.25% to build a box that keeps an aspect ratio came from this (now there is aspect-ratio).
And min-width and max-width are applied after width. If you use width: 100% and max-width: 640px together, you get a fluid box that never exceeds 640px. CSS Values defines the units and the calculation order.
What it looks like in the field
Here are two things that actually happened in this service.
First, the time only the body stuck out of the screen. body was set up as a flex column and the body was given margin: 0 auto, but when a flex child gets an automatic margin, stretching is turned off. When one wide code block came in, only the body widened to that width and went out of line with the top bar. It was fixed by specifying width: 100% explicitly.
Second, the time the card grid fit only two per row. It had padding: 16px on width: 33.33%, but box-sizing was the default. The sum of three exceeded 100%, so the line wrapped. One line of border-box finished it.
In both cases, if you don't know the calculation rules it ends at "CSS is weird," and if you do, one look at the computed values in the developer tools is all it takes.
What to check in the next quiz
You check the width a box actually takes up in the two box-sizing modes, the three cases in which margin collapsing happens and how to stop it, and what percentages are measured against.