You set width: 300px on a card, add padding: 20px and a 1px border, and the card measures 342px in DevTools. It overflows its 300px column by 42px and the layout breaks. Nothing is wrong with the browser; it’s following the default box model, and the default is not what most people assume.
Where the extra pixels come from
Every element generates a box with four layers, from inside out: content, padding, border, margin. The box-sizing property decides which of those layers width and height describe.
The default is box-sizing: content-box. Under it, width sets the content area only. Padding and border are added outside it:
.card {
width: 300px;
padding: 20px;
border: 1px solid #d4d4d8;
margin: 16px;
}The rendered width of the border box is:
content 300
padding 20 + 20 = 40
border 1 + 1 = 2
-----
total 342pxThe margin adds another 16px each side to the space the card occupies, so it claims 374px of horizontal room, but margin is outside the box and is never counted by box-sizing in either mode. DevTools’ box-model diagram (in the Computed tab in Chrome and Firefox) shows these four layers with their numbers, and it’s the quickest way to see what’s happening.
Now switch the model:
.card {
box-sizing: border-box;
width: 300px;
padding: 20px;
border: 1px solid #d4d4d8;
}With border-box, width describes the border box, so the card renders exactly 300px wide. The browser works out the content area by subtraction: 300 − 40 − 2 = 258px for the text inside. The total stays put, and padding eats into the content instead of pushing the edges outward.
Heights follow the same rule. height: 100px with 20px vertical padding gives a 140px box under content-box and a 100px box (60px of content) under border-box.
Why border-box is easier to reason about
The case that convinces people is a percentage width combined with fixed padding:
.column {
width: 50%;
padding: 0 24px;
float: left; /* or display: inline-block, or a flex item with a fixed basis */
}Two of these should sit side by side. In a 1000px container under content-box, each column is 500px of content plus 48px of padding, 548px in total. Two of them need 1096px, so the second one wraps underneath. There’s no fixed pixel value you can subtract to fix it, because the column width is a percentage and the padding is in pixels. Before box-sizing existed, people used calc(50% - 48px) or nested a padded inner div.
Under border-box, each column is exactly 500px including its padding, and two of them fit. The padding can be any unit without breaking the arithmetic.
The same thing makes width: 100% safe on form inputs. A text input with width: 100%; padding: 8px; border: 1px solid overflows its parent by 18px under content-box, which is why inputs poking out of their containers on the right is such a common sight in older stylesheets.
The global reset
Because border-box is easier almost everywhere, projects usually switch it on for everything. The version you’ll see most often is:
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}Why inherit instead of setting border-box on * directly? Because it lets a component switch back for its whole subtree with one declaration. If a third-party widget was built assuming content-box, you set .legacy-widget { box-sizing: content-box; } and all its descendants inherit that. With * { box-sizing: border-box; }, every descendant would have its own explicit border-box and you’d need to override each one.
The ::before and ::after selectors are there because * doesn’t match pseudo-elements. Without them, a decorative ::after with padding would still use content-box and could be off by its padding.
Some modern resets simply use *, *::before, *::after { box-sizing: border-box; }, and that’s also fine when you control all the CSS on the page. In an interview, explaining the trade-off is worth more than reciting either version.
Things that surprise people
Some elements are already border-box. Browsers’ user-agent stylesheets give several form controls border-box sizing (<select>, buttons and some input types, depending on the browser). That’s part of why a <select> and an <input> with the same width and padding could render at different sizes before the reset was common.
Content can’t go below zero. With border-box, if padding plus border exceed the width (width: 30px; padding: 20px), the content area would be −10px. Browsers clamp it to 0 and the box renders 40px wide, ignoring the width you asked for.
Inline elements ignore it. A <span> or <a> with the default display: inline doesn’t accept width or height at all, so box-sizing has nothing to act on. Its horizontal padding and border still push neighbouring text aside, and its vertical padding paints over the lines above and below without moving them. If you want a link to have a fixed size, give it display: inline-block (or make it a flex or grid item) first; then box-sizing applies as usual.
Min and max sizes follow the same model. min-width, max-width, min-height and max-height are measured in the same box that box-sizing selects. So max-width: 600px on a border-box element caps its border box at 600px.
Flex and grid sizing use it too. flex-basis: 200px refers to the box set by box-sizing, so the reset affects flex items’ starting sizes as well.
Outline and box-shadow aren’t part of any box. They’re painted outside the border and take no layout space, so they never change an element’s measured size, under either model. That’s why focus outlines don’t shift the layout when they appear.
Interview practice
A 320px wide container has two inputs side by side, each with width: 50%, padding: 10px and border: 2px solid. They wrap onto two lines. The page uses the default box-sizing. Explain what’s happening and give two fixes.
Answer
Under the default content-box, width: 50% sets each input’s content area to 160px. Padding adds 20px and borders add 4px, so each input is 184px wide and the pair needs 368px, which doesn’t fit in 320px, so the second one wraps.
The first fix is box-sizing: border-box on the inputs (or globally via the inherit reset), after which each input’s border box is exactly 160px with a 136px content area, and two fit exactly. The second fix, if you can’t change the box model, is width: calc(50% - 24px), which subtracts the padding and border so the total is back to 160px. The first is better because it doesn’t hard-code the padding and border values into the width, so changing the padding later doesn’t break the layout again. (A third option, putting the inputs in a flex or grid container with 1fr columns or flex: 1, also works, but the flex item’s basis still depends on box-sizing, so it’s good to understand the box model rather than reach around it.)