Skip to content

The box model

docs.scrimba.com

Once you start spacing things out with CSS, you run into the same puzzle everyone does: you set a width, add some padding, and the element comes out wider than you asked for. Two paragraphs sit closer together than the numbers say they should. None of this is a bug. It is the box model, the set of rules that decides how much room every element takes up, and once you can picture it, most of your spacing surprises stop being surprises.

Every element is a box

CSS treats every element on the page as a rectangle, even a single word or a link. That rectangle is built from four layers stacked from the inside out, and this is the box model: the content in the middle, then padding around it, then a border, then margin on the outside.

Picture a framed photo on a wall. The photo is the content. The white mat around the photo is the padding, breathing room inside the frame. The wooden frame itself is the border. And the empty wall between this frame and the next one is the margin. Every element you style is packaged the same way, from the inside out.

css
.card {
  padding: 16px;              /* space inside, around the content */
  border: 2px solid #333;     /* the frame */
  margin: 24px;               /* space outside, pushing neighbours away */
}

Every element the browser renders is a rectangular box, and that box has four parts measured outward from the centre: the content box (where your text or image lives), the padding, the border, and the margin. You have seen this idea already in how CSS works; the box model is the part that governs size and spacing.

The order matters because each layer wraps the one before it. Padding sits between the content and the border, so a background colour fills the content and the padding but stops at the border's outer edge. Margin sits outside the border and is always transparent, which is why you cannot give a margin a colour.

css
.card {
  padding: 16px;              /* inside the border, shares the background */
  border: 2px solid #333;
  margin: 24px;              /* outside the border, always transparent */
  background: #f4f4f4;       /* fills content + padding, stops at the border */
}

Every rendered element generates at least one rectangular box, and the box model is the specification for its four nested regions. From the inside out: the content box (the area holding the element's text, image, or child boxes), the padding box (content plus padding, the transparent space that still carries the element's background), the border box (padding box plus the border, the visible edge), and the margin box (border box plus margin, the outermost transparent region that separates the element from its neighbours).

Two consequences fall out of this nesting that trip people up later. First, backgrounds paint the padding box, not the margin box, so padding gives you clickable, colourable interior space while margin gives you empty separation you cannot style. Second, the width and height you declare do not describe the whole box by default; they describe only one region of it, and which region depends on box-sizing, which the next sections make precise.

css
.card {
  padding: 16px;
  border: 2px solid #333;
  margin: 24px;
  background: #f4f4f4;       /* paints content + padding, never margin */
}
JunoEvery element is a box Every element is a box with four layers from the inside out: content, then padding, then border, then margin. The framed-photo picture stuck with me: photo, mat, frame, gap to the next frame. Get that picture in your head and the rest of this chapter follows from it.
JunoEvery element is a box Content, padding, border, margin, wrapping outward in that order. The background fills the content and padding and stops at the border, and margin is always transparent, which is why margin takes no colour. Keep the nesting straight and the spacing rules stop feeling arbitrary.
JunoEvery element is a box Four nested regions: content box, padding box, border box, margin box. The background paints out to the border box and never the margin, so padding is colourable interior and margin is empty separation. And your declared width only ever sizes one of these regions, which is the whole reason the next section on box-sizing exists.

padding, border, and margin

The three spacing layers each do one job. Padding is space on the inside, between the content and the border. Margin is space on the outside, pushing other elements away. The border is the line drawn between them.

You can set one value for all four sides at once, or target a single side:

css
.card {
  padding: 20px;             /* all four sides get 20px */
  margin-bottom: 32px;       /* only the bottom edge */
}

The shorthand padding: 20px is a quick way to say top, right, bottom, and left all together. When you need one side different from the rest, reach for the specific property like margin-bottom.

Padding is space inside the border, margin is space outside it, and the border is the edge between them. The rule to keep is that margin pushes neighbours away, padding grows the element's own interior. If a background looks cramped against its text, that is a padding job. If two elements sit too close, that is a margin job.

Each takes a shorthand that fills sides clockwise from the top. One value applies to all four sides, two values set vertical then horizontal, four values go top, right, bottom, left:

css
.hero {
  padding: 40px 24px;        /* 40px top and bottom, 24px left and right */
  margin: 0 auto;            /* no vertical margin, auto left and right to centre */
}

.card {
  padding-left: 16px;        /* per-side when only one edge differs */
}

Padding, border, and margin are three independent spacing controls with different behaviours, not three names for the same gap. Padding is the transparent region inside the border that still carries the background and still responds to clicks. Margin is the transparent region outside the border that separates the element from its siblings and does not carry the background. The border is the drawn edge that also takes up layout space equal to its width.

All three accept the clockwise shorthand (one, two, three, or four values expanding to top / right / bottom / left), and all three have per-side longhands. Two behaviours are worth holding precisely. Margins may be negative, which pulls an element toward or over a neighbour and is a legitimate layout tool, while padding may not go below zero. And margin: 0 auto centres a block horizontally only when the element has a definite width, because auto resolves by splitting the leftover horizontal space, and there is no leftover space to split if the box already fills its container.

css
.hero {
  padding: 40px 24px;        /* clockwise pairs: vertical, horizontal */
  margin: 0 auto;            /* centres only with a set width on .hero */
  border-bottom: 1px solid #ddd;  /* border longhand, one edge, takes 1px of layout */
}

.card {
  margin-top: -8px;          /* negative margin pulls the card upward */
}
Junopadding, border, and margin Padding is space inside the border, margin is space outside it, and the border is the line between. Use padding when text feels cramped inside a box, and margin when two boxes sit too close together. One value like padding: 20px covers all four sides, or name a side like margin-bottom when only one needs to change.
Junopadding, border, and margin Padding grows the element's own interior, margin pushes its neighbours away, and knowing which one you want saves a lot of guessing. The shorthand runs clockwise from the top: one value for all sides, two for vertical then horizontal. When a background hugs its text too tightly it is padding you want, not margin.
Junopadding, border, and margin Padding carries the background and clicks, margin is transparent separation, and the border takes real layout space equal to its width. Margins can go negative to pull elements together, padding cannot. And margin: 0 auto only centres a block that has a definite width, because auto has no leftover space to split otherwise.

box-sizing: content-box vs border-box

Here is the surprise that catches almost everyone. You set a card to width: 300px, add some padding, and the card ends up wider than 300 pixels on the page. You did not do anything wrong. By default, width only measures the content in the middle, and any padding and border get added on top.

There is a one-line fix that makes width behave the way you expected. Set box-sizing: border-box, and now the width you type is the full width of the box, padding and border included:

css
.card {
  box-sizing: border-box;    /* width now includes padding and border */
  width: 300px;
  padding: 20px;             /* the card stays 300px wide, not 340px */
}

By default every element uses box-sizing: content-box, which means the width you set applies to the content box only. Padding and border are then added outside it, so the box you actually see on the page is wider than the number you typed. This is the classic sizing gotcha behind a hundred blown-out layouts.

Do the maths once and it is clear. With content-box, a 300px width plus 20px padding on each side plus a 2px border on each side renders at 300 + 40 + 4 = 344px. Switch to border-box and width now means the border box: the padding and border are subtracted from the inside, so the element stays exactly 300px and the content shrinks to fit.

css
.card {
  box-sizing: content-box;   /* the default */
  width: 300px;
  padding: 20px;
  border: 2px solid #333;
  /* actual rendered width: 300 + 20 + 20 + 2 + 2 = 344px */
}

Because this is almost always what you want, the standard move is to set it globally with the universal selector so every element opts in:

css
* {
  box-sizing: border-box;    /* one rule, predictable sizing everywhere */
}

box-sizing decides which region your width and height measure, and it has two values. The initial value is content-box: width sets the content box, and padding and border are laid out outside that width, so the border box, the actual footprint on the page, is width + padding-left + padding-right + border-left + border-right. The alternative is border-box: width sets the border box directly, and the browser subtracts padding and border from the inside, shrinking the content box to whatever remains.

The arithmetic is worth stating exactly, because it is the whole reason the property exists. Given width: 300px; padding: 20px; border: 2px:

css
/* content-box (default): padding and border add on top */
.card {
  box-sizing: content-box;
  width: 300px;
  padding: 20px;
  border: 2px solid #333;
  /* border box = 300 + 40 + 4 = 344px; content box = 300px */
}

/* border-box: padding and border eat into the declared width */
.card {
  box-sizing: border-box;
  width: 300px;
  padding: 20px;
  border: 2px solid #333;
  /* border box = 300px; content box = 300 - 40 - 4 = 256px */
}

Recommend border-box: it makes sizing composable. The reason is practical: with border-box, a set of columns declared at width: 25% stay at a quarter of the container no matter what padding you add, because the padding no longer changes the outer size. That predictability is why the near-universal reset applies it to everything, usually including pseudo-elements, so nothing slips back to content-box:

css
*, *::before, *::after {
  box-sizing: border-box;
}

One precision note: this only governs width and height. Margins are always outside the border box regardless of box-sizing, so they never count toward the declared size, which connects directly to margin collapsing below. If you need units to size these boxes reliably, sizing units covers how percentages, rem, and viewport units resolve.

Junobox-sizing: content-box vs border-box By default, the width you set only measures the content in the middle, so padding and border make the box bigger than the number you typed. Setting box-sizing: border-box fixes that: the width becomes the full box, padding and border included. This one caught me out for ages, so it is worth remembering early.
Junobox-sizing: content-box vs border-box The default content-box means width sizes only the content, so padding and border push the real box wider than you asked. A 300px card with 20px padding and a 2px border actually renders at 344px. Switch to border-box and the width means the whole box, which is why setting it on * globally is the standard first line of most stylesheets.
Junobox-sizing: content-box vs border-boxcontent-box adds padding and border outside your width, so a 300px box becomes 344px; border-box subtracts them from the inside and keeps it at 300px. Reach for border-box because percentage widths stay composable when padding no longer changes the outer size. Apply it to * plus the pseudo-elements so nothing quietly falls back to the default.

Margin collapsing

One more surprise, and it is about margins stacking. Put two paragraphs on top of each other, give the first a 24px bottom margin and the second a 16px top margin, and you might expect 40px of gap between them. You actually get 24px.

This is called margin collapsing. When two vertical margins meet, the browser does not add them together. It keeps the larger of the two and throws the smaller one away. So the bigger margin wins, and the gap is 24px, not 40px. It only happens with top and bottom margins, never with padding.

css
.intro {
  margin-bottom: 24px;
}
.details {
  margin-top: 16px;
  /* the gap between them is 24px, the larger margin, not 40px */
}

When the bottom margin of one block meets the top margin of the next, the two do not add up. They collapse into a single margin equal to the larger of the two. Give one paragraph margin-bottom: 24px and the next margin-top: 16px, and the space between them is 24px, not 40px. This is margin collapsing, and it only ever affects vertical margins.

The reason to know it is that it changes how you reason about spacing. If a gap is smaller than the two numbers suggest, you are probably watching a collapse, and adding to the smaller margin does nothing until it passes the larger one. The clean way to control vertical rhythm is to set margin in one direction only, for example a margin-bottom on every block and no margin-top, so collapsing has nothing to resolve.

css
.article > * {
  margin-top: 0;
  margin-bottom: 24px;       /* one direction, so gaps are predictable */
}

Margin collapsing is the rule that adjoining vertical margins in normal flow merge into one, whose size is the larger of the two (or the most positive, with negatives handled by summing the largest positive and the most negative). Normal flow is the default top-to-bottom stacking of block boxes before any flex, grid, or float changes it. Give .intro a 24px bottom margin and .details a 16px top margin and the gap is 24px, because the browser resolves both into a single collapsed margin.

The precise part is knowing what does and does not collapse, because the escapes are how you stop a surprise:

  • It affects vertical margins only. Horizontal margins never collapse; two side-by-side elements always add their facing margins.
  • Anything between the two margins stops the collapse: a border, any padding, or content on the parent between a child's margin and its own.
  • Margins do not collapse across a formatting context boundary. A flex or grid container establishes one for its children, so items inside it never collapse their margins, which is one reason modern layouts feel more predictable than float-era ones.
  • A parent and its first or last child can collapse through each other when nothing separates them, so a child's top margin can unexpectedly appear outside the parent. Adding padding-top or a border to the parent stops it.
css
/* Collapse happens: adjacent siblings in normal flow */
.intro { margin-bottom: 24px; }
.details { margin-top: 16px; }   /* gap resolves to 24px */

/* Collapse stopped: a border between the margins */
.section {
  border-top: 1px solid #eee;    /* now both margins apply, no merge */
}
JunoMargin collapsing When a bottom margin meets a top margin, the browser keeps the larger one and drops the smaller, instead of adding them. So 24px below meeting 16px above gives a 24px gap, not 40px. It only happens with top and bottom margins, and it surprises everyone the first time.
JunoMargin collapsing Vertical margins between blocks collapse to the larger of the two, never the sum, so a gap can be smaller than the numbers suggest. When that happens, growing the smaller margin does nothing until it overtakes the larger. Setting margin in one direction only, like margin-bottom on every block, sidesteps the whole thing.
JunoMargin collapsing Adjoining vertical margins in normal flow merge to the larger value, and horizontal margins never do. A border or padding between them stops the collapse, and flex and grid containers stop it entirely because they make a new formatting context. Watch the parent-and-first-child case, where a child's top margin escapes the parent until you add padding-top or a border to hold it in.