Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. The CSS box model: what the numbers actually do
Web Development

The CSS box model: what the numbers actually do

A box set to 200px wide can render at 250px. Here is the box model measured in a real browser, and why the computed width will not tell you.

By Max Arthur
Co-Founder & Content Marketer·August 13, 2026·7 Min read
One box in content-box sizing rendering wider than its declared width beside an identical box in border-box sizing rendering at its declared width

Every element on a page is a rectangular box built from four layers, working outward: the content, the padding around it, the border around that, and the margin outside everything.

The part that catches everyone is what width refers to. By default it sets the width of the content only, so padding and border are added on top and the box renders wider than the number you wrote.

#a {
  width: 200px;
  padding: 20px;
  border: 5px solid black;
}

That box does not occupy 200 pixels. Measured in a real browser, it occupies 250: 200 of content, 20 of padding on each side, and 5 of border on each side.

Every number on this page came from getBoundingClientRect() in headless Edge.

The four layers

Layer What it is Does it have a background?
Content The text, image or child elements Yes
Padding Space inside the border Yes
Border The frame itself It is the frame
Margin Space outside the border No, always transparent

The last column is the practical difference between padding and margin, and it is the one to remember when choosing between them. Padding is inside the box and takes the box's background colour. Margin is outside and shows whatever is behind the element.

So if you want a coloured element to look bigger, use padding. If you want space between two elements, use margin.

content-box and border-box, measured

Two boxes with identical declarations except for box-sizing:

#a { width: 200px; padding: 20px; border: 5px solid black; }
#b { width: 200px; padding: 20px; border: 5px solid black;
     box-sizing: border-box; }
#a content-box #b border-box
Rendered width 250px 200px
offsetWidth 250 200
clientWidth 240 190

content-box is the default and the source of the surprise: width sets the content, and everything else is added.

border-box makes width mean the whole visible box. The padding and border are taken out of the 200 instead of added to it, so the content shrinks to 150 and the box renders at exactly the number you asked for.

clientWidth is worth understanding while you are here, because it measures a third thing: content plus padding, excluding the border. For #a that is 200 + 40 = 240. For #b it is 200 - 10 = 190.

The computed width will not tell you

Here is the part that makes this genuinely hard to debug. Both boxes report the same computed width:

getComputedStyle(document.querySelector("#a")).width; // "200px"
getComputedStyle(document.querySelector("#b")).width; // "200px"

One of those boxes is 250 pixels wide on screen. The other is 200. They report identically.

That is why "check the computed width in DevTools" does not resolve this particular confusion, and why the box model diagram in the DevTools styles panel is the thing to look at instead: it shows content, padding, border and margin as separate measured numbers.

In code, offsetWidth is the value that reflects what you can see, because it includes padding and border. When a layout is 50 pixels wider than you expected and the CSS looks right, compare offsetWidth against getComputedStyle().width and the gap is your padding and border.

The global reset

Because border-box is what almost everyone wants almost all the time, most projects turn it on everywhere:

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

This is close to universal in modern CSS, and it is the first rule in most reset stylesheets. It makes width: 50% mean 50% of the space regardless of what padding you later add, which is what makes layout arithmetic stay simple as a design changes.

The pseudo-element selectors matter. Without them, ::before and ::after keep the default sizing and behave differently from everything else, which produces a very confusing bug when you eventually use them.

width: 100% is usually the wrong instinct

This is the box model's most common practical failure. A child set to fill its parent, with some padding:

.parent { width: 400px; }
#full   { width: 100%; padding: 20px; }

Measured inside a 400px parent, that child renders at 440px. It overflows by exactly the padding, which is where an unexpected horizontal scrollbar usually comes from.

Now the same element with no width declared at all:

#noWidth { padding: 20px; }

That measures 400px. It fits perfectly.

Declaration Rendered width in a 400px parent
width: 100% with padding 440
width: 100% with padding, border-box 400
no width, with padding 400

A block element already fills the width available to it, and it accounts for its own padding while doing so. Adding width: 100% overrides that sensible default with a worse one, because a percentage is measured against the parent and the padding is then added on top.

So the fix is usually to delete a line rather than add one. width: 100% earns its place when you are overriding a narrower width, or on an element that is not block-level by default such as an <input>. On an ordinary <div>, leaving width alone is both simpler and more correct.

Margins collapse, and the numbers are not intuitive

Two stacked elements, one with 30px below it and one with 20px above it:

#m1 { margin-bottom: 30px; }
#m2 { margin-top: 20px; }

The measured gap between them is 30px, not 50.

Vertical margins between block elements collapse into a single margin, and the larger one wins. They are not added. This is standard behaviour rather than a bug, and it is why setting margin-bottom: 20px on every paragraph gives you 20px between paragraphs rather than 40.

Three things to know about it:

  • It applies to vertical margins only. Horizontal margins never collapse.
  • It does not happen inside a flex or grid container, which is one reason gaps behave more predictably in modern layouts.
  • gap does not collapse either, which makes it the more reliable tool for spacing a list of things. There is more on that in the flexbox guide.

Two ways a box ignores your numbers

Inline elements ignore width and height. A <span> with both set to 300px measured 69.77 by 17 pixels, which is simply the size of its text:

#inl { width: 300px; height: 300px; }

Nothing is broken. Inline elements are sized by their content by design. If you need the dimensions, change the element's display with display: inline-block or display: block, or use a <div>.

A percentage height needs a parent with a height. The same height: 50% measured 100px inside a 200px parent and 18px inside a parent with no height set. In the second case the parent's height depends on its children, so a child asking for half of the parent is circular, and the rule is ignored. The 18px is just the line of text.

This is the real reason "my element will not fill the height" is such a common problem. The fix is to give the parent a real height, or to use a viewport unit such as 100dvh, or to let a flex or grid container handle the stretching. It is the same root cause behind several of the failures in how to center a div.

The short version

  • width sets the content by default, so padding and border make the box bigger.
  • box-sizing: border-box makes width mean the whole box. Turn it on globally.
  • offsetWidth reflects what you see; getComputedStyle().width does not.
  • Vertical margins collapse to the larger of the two.
  • Inline elements ignore width and height, and percentage heights need a parent that has one.

Ready to push boxes around and watch the numbers change? Start with the CSS track and measure them yourself.

Keep reading

More from the blog

Three flex children sized unevenly by their content beside three grid children sized equally by their tracks
September 4, 2026·11 min readWeb Development

Flexbox vs Grid: how to choose in ten seconds

Flexbox vs grid: flex sizes items to their content, grid sizes the tracks. Measured, that gave 32px and 229px against three equal 193px columns.

Read more
Two competing CSS rules with their specificity scored as three numbers, showing which one wins and why
August 18, 2026·6 min readWeb Development

CSS specificity: why your style is not applying

Specificity is three numbers compared left to right, not one score. Here are the contests that decide it, measured in a browser.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs MimoDevpuff vs Sololearn
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs freeCodeCamp
Read All Comparisons