Why a block is wider than you set: the box model in plain words
August 28, 2026 · 8 minutes · Dediu Pavel
You set a width and the block came out different and pushed past its parent. We look at how the browser really calculates size, and how to find the culprit in the developer panel in half a minute.
The symptom: I set width: 100% and the block spilled off the screen
It looks like this. A horizontal scrollbar has appeared at the bottom of the page. Or a column has moved past the right edge. Or four cards, 300 pixels each, did not fit into a container 1160 wide, even though 300 × 4 = 1200 and on paper it all added up. And there is not a single typo in the CSS: the values are exactly the ones you wrote.
It is worth clearing the browser of suspicion straight away: it calculates honestly and identically in every tab. The discrepancy is between what you meant by the word «width» and what CSS means by it. Those two values are different by default, and until you say otherwise, the default applies.
The cause is almost always the same: writing width: 300px does not mean «a block 300 pixels wide». It means «300 pixels for the block's content», and padding and borders are added on top. Let us look at where the difference comes from, so that from now on you get the number you need by calculation rather than by trying width: 96%.
What the browser actually counts as width: content-box by default
Every block has four zones, strictly in this order from the centre outwards. content — the content area, and by default that is what width and height set. padding — the inner space between the content and the border, painted with the element's background. border — the border, which takes up space even when it is transparent. margin — the outer space beyond the border, always transparent. Each zone is added on all four sides.
The width taken up adds up like this: width plus padding left and right plus border left and right. Let us count on a card with width: 300px, padding: 24px and border: 1px solid: 300 + 24 + 24 + 1 + 1 = 350 pixels. A row of four such cards takes up 1400 — 240 more than the container, and that is before any gaps between them. The code says 300, the arithmetic in your head adds up, and on the screen it does not.
The classic version of the same mistake is width: 100% together with padding: 24px on one element. The hundred per cent is counted from the parent's width, and 48 pixels of padding are added to it. The block is guaranteed to be wider than its parent by exactly its padding, and a horizontal scrollbar appears.
border-box: what changes and why it is set globally
The box-sizing property switches what exactly counts as the width. The default value is content-box: width refers to the content, padding and borders go on top, and our 300 becomes 350. The value border-box: width is the full width including padding and borders, our 300 stays 300, and the content gets 300 − 48 − 2 = 250.
The second option matches the way a person thinks about sizes: «the card is 300, with 24 of padding inside». That is why border-box is set globally in one line at the top of the file: *, *::before, *::after { box-sizing: border-box; }. The pseudo-elements are listed separately because the asterisk does not cover them.
It is worth understanding what that line does not do. It does not «make blocks smaller» and it does not «fix the layout» — it makes the number in the CSS and the number on the screen the same. After it, 4 × 300 = 1200 — still more than 1160, and it is immediately clear that three cards fit in a row, not four. Counting became possible in your head, and that is the only reason for the global rule.
It helps to look at this from the other side too: with border-box the card's content gets 250 pixels, and everything inside is measured from that number — the line length, the size of the photograph, the width of a full-card button. With content-box you know the inner number but not the outer one, and the grid ends up eyeballed every time.
Alongside this it is worth remembering the limits. width is the width you want, while min-width and max-width are boundaries, and they are stronger: max-width beats width, and min-width beats both. Hence the obligatory line for images: an img has its own size in pixels, and a photograph 1600 wide will tear apart a 300 column unless you write max-width: 100%.
Why border-box does not save you from margin
margin is not part of a block's width under any value of box-sizing: in the diagram it lies outside the border. But it does take up space in the flow, so in a row calculation it is added as a separate term: three cards of 300 with gaps of 24 is 300 × 3 + 24 × 2 = 948.
The consequence people trip over regularly: width: 100% together with margin: 16px always overflows. The hundred per cent is counted from the parent's width, the margins are added to it, and border-box has nothing to do with it — it is about the content inside the border, not about what is outside.
The right solution is usually not a hunt for percentages. A block element already takes up the whole available width of its parent, so width: 100% is most often simply not needed: remove the line and the margins will stop pushing the block outwards. The second option is to move the spacing to the parent as padding. The third, and the most predictable, is to set distances with gap on a flex or grid container, where this arithmetic does not arise at all.
Margin collapsing: it is not a bug, here is the rule
The other half of the questions about sizes sound different: «the margin disappeared» or «the margin escaped outside». You gave a section heading margin-top: 48px, and the space appeared not inside the section but above it: the section itself, background and all, moved down, and between its top edge and the heading there is nothing. That is vertical margin collapsing, behaviour from the specification, always on until you do something that switches it off.
There are exactly three cases. Adjacent siblings: the bottom margin of the upper element and the top margin of the lower one overlap, and the larger of the two remains, not the sum — 24 and 16 give 24, not 40. A parent and its first or last child: if there is neither padding nor border between the child's margin-top and the parent's top border, the margin escapes outside and moves the parent itself down. An empty block: in a block with no content, padding, border or height, its own top and bottom margins collapse into one.
The rule looks arbitrary until you look at text. A paragraph has margins above and below, roughly a line each. Without collapsing there would be double the distance between two adjacent paragraphs. Collapsing turns «the margin below» and «the margin above» into one distance between elements.
What does not collapse: horizontal margins — never; elements inside a flex or grid container, where neighbours' margins add up; elements with position: absolute and fixed; blocks with their own formatting context — overflow: auto or hidden, display: flow-root, display: inline-block; and any case where there is padding or a border between two margins, even one pixel of it.
You can check this for yourself in a minute. Take a section with a background and a heading that has a top margin, and give the section padding-top: 1px. The margin instantly returns inside: padding has appeared between the child's margin and the parent's edge, the contact is gone, and there is nothing left to collapse. One pixel is not a solution for production code, but it is the best proof that collapsing is what is going on.
The practical conclusion is shorter than the theory: choose one direction and stick to it. If every vertical distance is set with margin-bottom, collapsing between siblings has nothing to overlap — the margin is always single and always the one you wrote. The rule «we never space upwards» saves more time than a detailed knowledge of the specification.
How to find the culprit in 30 seconds in DevTools
There is no need to guess, it is all visible in the browser. Right-click the block, choose «Inspect», then open the Computed tab. At the very top there is the box model diagram: four nested rectangles from margin on the outside to content in the centre, with numbers on every side.
Then three steps. First: look at the central rectangle — that is the real size of the content. If it equals your width rather than being smaller by the padding, you are in content-box. Second: find the box-sizing line further down the list of properties — it says outright which rule the width is calculated by. Third: hover over any zone of the diagram and the matching area is highlighted on the page in its own colour: content blue, padding green, border yellow, margin orange. An orange highlight drawn beyond the parent is exactly the margin that escaped.
The same technique tells collapsing apart from a typo. If Computed shows the margin-top you wanted and there is nothing above the element — that is collapsing, and the CSS is correct. If Computed shows zero — look for the mistake: a typo in the class name, forgotten units (margin-top: 48 without px will not apply) or a stronger selector that beat yours; losing properties are struck through in the Styles panel.
This is exactly the way of working the «Markup without magic» course is built on: we take the flow, the box model, flex and grid down to their causes rather than to recipes, and among the course outcomes there is a separate line, «explain why a block ended up exactly that size». A whole lesson goes on the box model there, with calculations on paper and checks against the diagram in Computed.
Five typical situations and what to fix in each
A summary that is handy to come back to.
What all five have in common: first work out what the number is made of, and only then fix it. Trying values is bad precisely because it fixes one screen width and leaves the mistake on all the others.
The same arithmetic exists in the design file too, just under different names: a container's inner padding and the gaps between elements are set there by numbers, not by dragging with the mouse. How that looks from the Figma side, and what to do when blocks fall apart in the design already, is covered in the article auto layout in Figma falls apart.
- A block with
width: 100%and inner padding pushed past its parent. A globalborder-boxat the top of the file. Do not hunt for percentages: 96% solves the problem at one screen width and breaks at another. - A row of cards does not fit, even though the widths add up. Recalculate the occupied width with padding and border, and add the gaps as a separate term. If counting is awkward — move the distances to
gap. - An image tore the column apart.
img { display: block; max-width: 100%; height: auto; }. Thedisplay: blockline also removes the gap under the image: by default it is inline and sits on the text baseline. - The margin escaped outside the section and the background starts in the wrong place. Collapsing. Give the section
padding— an honest solution when you need the air inside anyway — or removemargin-topfrom the first child altogether. - A horizontal scrollbar appeared on a phone. Look for an element with a fixed width larger than the screen, a negative margin or a long unbreakable word. A container is more reliably set as
width: min(1160px, 100% - 32px): it shrinks along with its side padding.
Markup without magic
11 modules · 22 hours · From scratch