Basics

What is the CSS box model — and why your block is wider than you set

Illustration: a ruler measures the item while the box around it runs past the mark

Here's the annoying part. You give a block width: 350px and a 10-pixel border. You open the page — the block takes up 370. You didn't break anything: the browser did exactly what the CSS standard says. It's just that width isn't the width of the block by default. It's the width of its filling.

Five minutes here, and half of your "the layout shifted" moments stop happening.

A block is four layers, not a rectangle

The browser draws every element as a box of four nested layers. From the center out:

  • content — the actual stuff: text, an image, nested blocks;
  • padding — inner spacing, the air between content and border;
  • border — the border itself;
  • margin — outer spacing, the air between this box and its neighbours.

That's the box model. The word "model" sounds academic, but the idea is everyday: a parcel. The item inside, bubble wrap around it, cardboard outside, and gaps between boxes on the shelf.

One thing to lock in right away: margin never counts toward the size of the box, under any setting. It lives outside. The argument is only ever about the first three layers.

Where it breaks: width measures the filling

By the standard, width and height set the size of the content box — that innermost layer. Padding and border get added on top.

Hence the arithmetic straight from MDN: a block with width: 350px and a 10px border takes 370 pixels — 350 of filling plus 10 on each side.

.card {
  width: 350px;
  border: 10px solid;
  /* on screen: 370px */
}

With one block that's tolerable. It hurts with percentages. The classic: four columns at width: 25%. That's exactly 100% — it should fit. You add padding: 16px to each, and the fourth column drops to a new line. Because each one is now 25% plus 32 pixels, and the row only has 100%.

Same story with width: 100%. The block matches its parent exactly, you add inner padding, and it spills out — dragging the page sideways.

One line that flips the logic

CSS has a property called box-sizing. Two values:

  • content-box — the default, the behaviour above;
  • border-boxwidth is measured to the outer edge of the border. Padding and border fit inside the number you gave, and the content shrinks to make room.

With border-box, that same block with width: 350px and a 10px border takes exactly 350 pixels on screen, leaving 330 for text.

Which is why almost every project opens with one line:

* {
  box-sizing: border-box;
}

After it, "25% plus padding" means a quarter of the row again, and the columns line up as intended. On Tailwind you already have it: Tailwind's base styles (Preflight) set border-box on every element by default, so this pain barely shows up there.

What border-box does not change: margin. It still sits outside and still adds to the space the block occupies in the flow. If four 25% columns still don't fit after border-box — go looking for margin, not padding.

Height plays by different rules

One more expectation-breaker: width and height aren't symmetric in the box model.

width: 50% works almost always — the browser knows the parent's width right away. But height: 50% often does nothing: by default the parent is exactly as tall as its content, so there's no 50% of anything. Hence the classic "percentage height doesn't work" — the parent needs a height of its own, say min-height: 100vh on the section.

And the truly counterintuitive one: percentages in padding and margin resolve against the parent's width — even the vertical ones. So padding-top: 10% is a tenth of the width, not the height. It looks like a bug, but it's what the standard says, and it's what the old fixed-aspect-ratio trick is built on. These days there's a proper aspect-ratio property for that, but if you spot padding-top: 56.25% in someone's code — now you know it's a 16:9 ratio.

How to see the box with your own eyes

Don't guess — open DevTools (F12), select the element, and find the Computed tab. At the bottom the browser draws that exact four-layer diagram with real numbers: how much content, padding, border, margin. Hover any layer and it highlights on the page.

This is also the first thing to do when an AI assistant generated your layout and it drifted. A model will happily write width: 100% next to padding: 24px — the exact combination that breaks for the reason in this article. The Computed diagram names the culprit in seconds, and then you ask for one specific fix instead of "fix my layout". More on that kind of triage in why AI-written code has bugs.

Everyday rule: width is a promise about the filling, unless you explicitly said otherwise.

Why don't four 25% columns fit on one line?

Because each column's padding and border get added to its 25%. Four times "25% + 32px" is more than 100%. Fixed by box-sizing: border-box — or by moving to gap in Grid or Flexbox, where the spacing between items isn't part of their width at all.

Does margin count inside border-box?

No. border-box covers content, padding and border — that's it. Margin is always outside the box, whatever box-sizing says.

Why keep content-box at all if border-box is nicer?

Backwards compatibility: content-box is the initial value in the standard, and millions of old pages are built around it. You can't change the default for the whole web — so each project turns border-box on for itself.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app

If styles still behave strangely after this, the box probably isn't the problem — it's more likely that another rule is overriding yours or the browser is serving cache.

KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →