The box model
In short: The box model says every element is a box of content, padding, border and margin; with box-sizing: border-box the width includes the padding and the border.
Problem
The cards stick out of the screen
The page with the breads on a phone 360px wide. Every card has width: 100%: as wide as the column. Yet the cards stick out on the right, and the whole page slides sideways.
Console
The column is 360 − 2 × 16 = 328px. The card was meant to be that wide, and it is 362px: 34px wider than the column. Those 34px are not a mistake in the number 100%. They are in what width measures.
The idea
Four layers
To CSS every element is a rectangular box. It has four layers, from the inside out:
- the content: the text, the image, the children;
padding: the space between the content and the border. The background of the element fills it;border: the frame;margin: the space outside the border, up to the neighbouring boxes. It is transparent.
By default width is the width of the content only. Padding and border are added outside, on both sides:
.card { width: 100%; /* 328px: the content */ padding: 16px; /* + 2 × 16 */ border: 1px solid #ccc; /* + 2 × 1 = 362px */}That is why the card from the start is 362px. padding: 16px is for all four sides. You can also set them separately: padding: 8px 16px (top and bottom, left and right) or padding-left: 16px.
See it
Watch the box
The box is built layer by layer. The line on top is width: watch what it measures.
/* the default: box-sizing: content-box */.card { width: 280px; padding: 24px; border: 8px solid #2b3a55; margin: 16px;}.card.bb { box-sizing: border-box;}Step by step, in words
- In the middle is the content: the text of the card. By default width is the width of the content only: 280px.
- padding is the space between the content and the border. It is on both sides: 280 + 2 × 24 = 328px. The background of the card fills it too.
- border is the frame, on both sides as well. The box is now 344px wide, not 280: width did not say how wide the card is on screen.
- margin is the space outside the border, between this box and the next one. It has no background and is not part of the card, but it takes room: 376px.
- box-sizing: border-box changes what width means: it is now the width up to the border. The card is 280px, and the content shrinks to 280 − 48 − 16 = 216px.
- The same CSS, two meanings of width. With content-box the card is 344px, with border-box it is 280px: what width says. That is why almost every project sets border-box for all elements.
The same two cards in the browser. The blue is the paragraph inside: it is as wide as the content of the card. In the developer tools (F12) select the card: the Computed panel draws the same box with the numbers of each layer.
Console
The idea
box-sizing: border-box
Doing sums in your head breeds bugs: you want a card as wide as the column, and you have to subtract padding and border. box-sizing: border-box changes what width means: it is the width up to and including the border. Padding and border are taken from the inside, and the content shrinks.
Then width: 100% really is the column, whatever the padding. That is why almost every project starts with:
*, *::before, *::after { box-sizing: border-box;}* matches every element. box-sizing is not inherited, so the rule is for all elements, not only for <html>. ::before and ::after are pieces of content that CSS adds by itself; they are boxes too.
Try it
Fit the cards
Make the cards fit the phone screen with box-sizing: border-box. The padding (16px) and the border (1px) stay.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Breads</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Breads</h1>
<article class="card">
<h2>Dobrudzha bread</h2>
<p class="price">€1.80</p>
<button type="button">Add to cart</button>
</article>
<article class="card">
<h2>Rye bread</h2>
<p class="price">€2.10</p>
<button type="button">Add to cart</button>
</article>
</main>
</body>
</html>
style.css*, *::before, *::after {
box-sizing: border-box;
}
body { font-family: system-ui, sans-serif; margin: 0; }
main { padding: 16px; }
.card {
width: 100%;
padding: 16px;
border: 1px solid #ccc;
margin-bottom: 12px;
}
With border-box, width: 100% is 328px together with the padding and the border, and the content gets 328 − 32 − 2 = 294px. width: 90% happens to fit on this screen, but on another one you would be doing sums again. Without padding the card fits, but the text touches the border.
The rule almost every project starts with:
*, *::before, *::after {
box-sizing: ;
}border-box: width and height go up to and include the border. The default value is content-box.
The idea
Margins collapse
Two boxes one below the other: the bottom margin of the upper one and the top margin of the lower one do not add up. They collapse, and the bigger one stays.
Console
- The notes have 24px below and 16px above. Between them there are 24px, not 40px.
- The heading in the banner has
margin: 20px 0. The banner has no padding or border on top, so the margin of the heading comes out above it: the orange background starts right above the text. Withpadding: 1px 16pxon the banner the background covers the margin too.
Only vertical margins collapse. On the left and right they always add up. In flex and grid, the next lessons, even vertical ones do not collapse.
The idea
block, inline and inline-block
Not every box behaves the same. The display property says how a box sits among the others.
block: starts on a new line and takes the full width of its parent. Width, height, margin and padding apply.div,p,h1andarticleare like this.inline: sits in the line of text and is as wide as its content. Width and height do not apply, a vertical margin moves nothing, and a vertical padding is painted but does not push the lines apart.span,aandstrongare like this.inline-block: sits in the line like inline, but width, height, margin and padding apply as for block.
Console
The two labels have the same properties, and the second one is also inline-block. The first one is inline: width: 64px and the margin do not apply, and the red of the padding sticks out above and below the line without pushing it away from the others. The second one is inline-block: it is 64 + 2 × 8 = 80px wide (it is still content-box here), and the paragraph gets taller to fit it.
Try it
The labels
The labels −20% and New must be 64px wide each and sit next to each other on one line. Right now the width does not apply.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fresh milk 3%</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<article>
<h2>Fresh milk 3%</h2>
<p><span class="badge">−20%</span> <span class="badge">New</span></p>
<p class="price">€1.92</p>
</article>
</body>
</html>
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
.badge {
display: inline-block;
width: 64px;
padding: 4px 0;
text-align: center;
color: #fff;
background: #c0392b;
}
display: inline-block gives the label the sizes of a box, and it stays in the line of text. With block the width applies, but each label is on a new line. inline is the default of a <span>, so it changes nothing.
The idea
Units
So far everything was in px. CSS has other units too, and each one is for something:
px: a fixed measure. Good for borders and small gaps.rem: the font size of<html>(16px, unless someone changed it).1.5remis 24px everywhere on the page. If a person made the font bigger in the browser settings, rem grows with it and px does not. So fonts and spacing are best in rem.em: the font size of the element itself; infont-sizeit is the parent's. Padding in em grows with the text of a button.%: from the parent.width: 50%is half the width of the parent's content.vwandvh: 1% of the width and of the height of the window.
Console
The heading is 1.5rem = 24px. The card is 50% of 320px = 160px. Its font is 18px, so padding: 1em is 18px. 1.25rem on the heading in the card is 20px: rem does not care about the 18px of the card. 0.75em on <small> comes from the font of the paragraph, which inherited 18px: 13.5px.
Try it
Find the bug
The menu is 20px, the submenu inside it is 25px, and the next level is bigger still. Which line is to blame?
Pick the line with the bug.
em in font-size is the size of the parent. Every nested .menu multiplies by 1.25: 16, 20, 25, 31.25. With font-size: 1.25rem every level is 20px.
More precisely, if you are curious
Percentages in padding and margin come from the width of the parent, even at the top and bottom. padding-top: 50% makes a box half as tall as its parent is wide.
100vw and the scrollbar. vw includes the scrollbar. If the page has a visible vertical scrollbar, an element with width: 100vw is wider than the page and causes a horizontal scroll. For "the full width", width: 100% is safer.
vh on a phone. The address bar shows and hides. 100vh is the height when the bar is hidden, so when the bar is showing, the bottom edge is below the screen. 100dvh follows the real height at the moment.
outline is drawn like a border but takes no space and moves nothing. That is why the focus of buttons is shown with it.
min-width and max-width set limits: max-width: 100% keeps an image from sticking out of the column.
Forms. In Chromium buttons are border-box by default, while fields (input) are content-box. The rule with * makes them the same.
How to stop collapsing. A child's margin does not come out through the parent if the parent has padding or a border on top, or if it is display: flow-root. That way the parent makes a new layout context (block formatting context).
A negative margin pulls the box outward and overlaps it with its neighbours. You rarely need it.
Exercise
The catalog on a phone
The catalog on a phone (360px). Set border-box on all elements so the cards fit the screen. The card: padding 1rem, a 1px solid #ddd border and 12px of space below each one. The product headings are 1.25rem. The labels −20% and New are inline-block, with padding 2px 8px, white text and a red background (#c0392b). The button is as wide as the inside of the card: width: 100%.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Breads</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Breads</h1>
<article class="card">
<h2>Dobrudzha bread</h2>
<span class="badge">−20%</span>
<p class="price">€1.80</p>
<button type="button">Add to cart</button>
</article>
<article class="card">
<h2>Rye bread</h2>
<p class="price">€2.10</p>
<button type="button">Add to cart</button>
</article>
<article class="card">
<h2>Spelt bread</h2>
<span class="badge">New</span>
<p class="price">€3.40</p>
<button type="button">Add to cart</button>
</article>
</main>
</body>
</html>
style.css*, *::before, *::after {
box-sizing: border-box;
}
body { font-family: system-ui, sans-serif; margin: 0; }
main { padding: 16px; }
.card {
width: 100%;
padding: 1rem;
border: 1px solid #ddd;
margin-bottom: 12px;
}
.card h2 { font-size: 1.25rem; }
.badge {
display: inline-block;
padding: 2px 8px;
color: #fff;
background: #c0392b;
}
.card button { width: 100%; }
*, *::before, *::after { box-sizing: border-box; } is the first rule: without it the cards are 362px. margin-bottom: 12px on the card gives the space. 1rem is 16px and 1.25rem is 20px, whatever is around them. The button with width: 100% is 294px: the column is 328px, minus 2 × 16 of padding and 2 × 1 of border of the card.
Test
Test yourself
Related lessons
Section titled "Related lessons"- Builds on: The HTML document · Selectors and the cascade
- Continues in: Flexbox: row and column · Grid: rows and columns · Responsive design
- Tasks on this topic: CSS: how the page looks