#The box model: content, padding, border, margin
AddedEverything CSS lays out is a box. Each element generates a rectangle (or none, with display: none), and that rectangle has four layers, from the inside out: the content area (where text and children go), padding (space inside the border, painted with the element's background), the border, and margin (space outside the border that pushes other boxes away and is always transparent).
A good mental model is a framed picture on a wall. The picture is the content. The mat around the picture is the padding: it belongs to the frame and shares its colour. The frame itself is the border. The empty wall you leave between two frames is the margin: it's not part of the frame, nothing is painted there, and it only exists to keep neighbours at a distance.
The one thing that trips people up: by default, width and height set the size of the picture only (the content box). Padding and border are added on top, so a width: 200px box with padding and a border is wider than 200px on screen. The next topic, box-sizing, is the switch that changes that.
.wall {
display: flow-root; /* keeps the child's margin inside the wall */
width: 340px;
background: #f1f5f9;
}
.box {
width: 200px;
height: 80px;
padding: 20px;
border: 5px solid #2563eb;
margin: 30px;
background: #dbeafe;
}<div class="wall">
<div class="box">content: 200 × 80</div>
</div>- The content box is exactly what
width: 200px; height: 80pxasked for, andgetComputedStyle(box).widthreports200px. padding: 20pxadds 20px on every side, so the padding box is240 × 120. That is whatclientWidthandclientHeightmeasure (content plus padding, no border):240and120.border: 5pxadds another 5px per side. The border box, the rectangle you actually see, is250 × 130:200 + 2×20 + 2×5wide and80 + 2×20 + 2×5tall.offsetWidthandgetBoundingClientRect().widthboth give250.margin: 30pxis outside the border: the box starts 30px from the wall's left and top edges, and nothing is painted there (you see the wall's grey through it).- The wall is
190pxtall:130for the box plus30above and30below. The wall hasdisplay: flow-rootso the child's top margin stays inside it; without it, the margin would escape through the wall's top edge (see Margin collapsing). - So the space this box claims in the layout is
310 × 190(border box plus margins), while itswidthproperty says200. Knowing which of those numbers each API reports is most of the box model.
A percentage in padding or margin is always a percentage of the containing block's width, even for padding-top and margin-bottom. That surprises people the first time, and it's the basis of the old aspect-ratio hack (see aspect-ratio).
.frame {
display: flow-root;
width: 400px;
height: 100px;
background: #f1f5f9;
}
.pct {
padding-top: 10%;
margin-top: 5%;
background: #fde68a;
}<div class="frame">
<div class="pct"></div>
</div>.frameis 400px wide and 100px tall. If percentages followed the axis,padding-top: 10%would be10px.- It computes to
40pxinstead: 10% of the width (400px).margin-top: 5%computes to20px, again from the width. - The empty
.pctbox is therefore40pxtall (all padding) and sits20pxbelow the frame's top edge. - The spec does this so that padding and margin on all four sides share one reference, which keeps an element's spacing uniform when you write
padding: 5%. The side effect is that a vertical percentage scales with width, which is exactly what makespadding-top: 56.25%a 16:9 box.
The box model applies fully to block boxes. Inline boxes (a <span>, an <a>) are different: their horizontal padding, border and margin push the text beside them, but vertical padding and border are painted without moving the lines, and vertical margins have no effect at all.
.line { width: 320px; margin: 0 0 8px; font-size: 16px; line-height: 24px; }
.tag { background: #fce7f3; border: 2px solid #db2777; }
.tall { padding: 12px 0; margin: 12px 0; }
.wide { padding: 0 12px; margin: 0 12px; }<p class="line" id="l1">Before <span class="tag" id="plain">word</span> <span id="a1">after</span></p>
<p class="line" id="l2">Before <span class="tag tall" id="tall">word</span> <span id="a2">after</span></p>
<p class="line" id="l3">Before <span class="tag wide" id="wide">word</span> <span id="a3">after</span></p>- All three paragraphs are exactly
24pxtall, theline-height. The.tallspan's12pxvertical padding and12pxvertical margin did not make its line any taller. - The
.tallspan's border box is still 24px taller than the plain span's: the padding is painted (the pink background and border grow up and down) but overlaps the lines above and below instead of pushing them. - The
.widespan is24pxwider than the plain one (12px padding each side), and itsmargin-right: 12pxmoves the following word 12px further away. - This is why
padding: 10pxon a link in a paragraph looks fine horizontally but overlaps the text above it. To give an inline element real vertical space, make itinline-block(see display).
outline and box-shadow are drawn around the border box but are not part of the box model: they take no space and never move anything. That makes outline the safe choice for focus rings and debug borders, because adding it can't shift the layout.
.row { display: flex; gap: 40px; padding: 30px; background: #f1f5f9; }
.row div { width: 60px; height: 40px; background: #dcfce7; }
.with-border { border: 10px solid #16a34a; }
.with-outline { outline: 10px solid #16a34a; box-shadow: 0 0 0 20px #bbf7d0; }<div class="row">
<div class="with-border"></div>
<div class="with-outline"></div>
<div class="next"></div>
</div>- The bordered box is
80pxwide: 60 of content plus 10 of border on each side. - The outlined box is still
60 × 40: the 10px outline and the 20px shadow are painted outside it, over the gap. - The next box starts exactly
40px(the flexgap) after the outlined box's border edge, so the outline and shadow moved nothing; they simply paint over neighbouring space.
Each DOM measurement stops at a different layer, which is a favourite follow-up question:
| API | What it measures | In the demo |
|---|---|---|
getComputedStyle(el).width | the width property's used value (content box under the default box-sizing) | 200px |
el.clientWidth | content + padding (minus any scrollbar), as a whole number | 240 |
el.offsetWidth | content + padding + border, as a whole number | 250 |
el.getBoundingClientRect().width | the border box as drawn, as a fraction, after transforms | 250 |
el.scrollWidth | the full width of the content including the part that overflows | see Overflow |
getBoundingClientRect() is the only one that includes transforms (a box scaled to 50% reports half its width) and keeps fractions; the client* and offset* properties round to integers.
.card { width: 300px; padding: 10px; border: 2px solid; margin: 20px; }How wide is the card's visible rectangle, and how much horizontal space does it take in the layout?
Show answer
The visible (border) box is 324px wide, and it takes 364px of horizontal space.
.shrink { display: flow-root; width: max-content; background: #f1f5f9; }
.card { width: 300px; padding: 10px; border: 2px solid #475569; margin: 20px; background: #fff; }<div class="shrink"><div class="card">card</div></div>- Under the default
box-sizing: content-box,width: 300pxis the content only. - Add padding on both sides (
2 × 10) and border on both sides (2 × 2):300 + 20 + 4 = 324px, the border box the test measures. - Margins sit outside that:
324 + 2 × 20 = 364px. The wrapper useswidth: max-contentto shrink-wrap the card including its margins, and measures364.