The box model
Every element on a page is a rectangle, even a full stop. The box model is the four
layers that rectangle is made of, and the reason width: 300px does not give you a
300-pixel box.
The four layers
From the inside out:
┌─────────────────────────────────────┐
│ margin (outside, invisible) │
│ ┌───────────────────────────────┐ │
│ │ border │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ padding │ │ │
│ │ │ ┌───────────────────┐ │ │ │
│ │ │ │ content │ │ │ │
│ │ │ └───────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
Content is the text or image. Padding is space inside, which the background covers. Border is a drawn line. Margin is space outside, which is always transparent — the background never reaches it.
That last distinction is the practical one: padding to push content away from the
edge, margin to push boxes away from each other. Give a coloured card padding
and the colour extends behind the space; give it margin and it does not.
The problem
.card {
width: 300px;
padding: 20px;
border: 2px solid #ddd;
}
How wide is that on screen? Not 300px. It is:
300 content
+ 40 padding (20 each side)
+ 4 border (2 each side)
= 344px
width sets the content box by default, and padding and border are added
outside it. So a row of three 33%-wide cards with any padding at all overflows its
container, and you end up guessing at percentages that do not quite work.
Worse, you cannot mix units to fix it — width: calc(33.33% - 44px) works until
somebody changes the padding.
The fix
*, *::before, *::after {
box-sizing: border-box;
}
Now width means the whole visible box — content, padding and border
together. width: 300px with 20px padding and a 2px border is 300px wide on
screen, and the content area shrinks to 256px to accommodate them.
This is the single most useful line of CSS ever written. Put it at the top of every stylesheet and never think about it again. It was in the reset in the previous lesson for this reason.
box-sizing is not inherited — it is a box property — which is why the rule needs
* and the two pseudo-elements rather than just body.
Shorthands, and the order
padding: 20px; /* all four sides */
padding: 10px 20px; /* vertical | horizontal */
padding: 10px 20px 30px; /* top | horizontal | bottom */
padding: 10px 20px 30px 40px; /* top | right | bottom | left — clockwise */
Clockwise from the top: TRouBLe — Top, Right, Bottom, Left. margin takes the
same forms.
The two-value form is the one you will use most: padding: 1rem 1.5rem is a
comfortable button.
Logical properties
padding-block: 10px; /* top and bottom */
padding-inline: 20px; /* left and right */
margin-inline: auto; /* the centring trick, below */
border-inline-start: 2px solid;
block is the direction text flows down, inline is the direction it reads. In
English those are vertical and horizontal — in Arabic or Urdu, inline-start is
the right. A site that might ever be translated should use these, and they are
shorter for the common vertical/horizontal case anyway.
Margin: the three surprises
1. Vertical margins collapse.
.a { margin-bottom: 30px; }
.b { margin-top: 20px; }
The gap between them is 30px, not 50 — the larger wins. Horizontal margins
never collapse. Nor do margins inside Flexbox or Grid, which is one reason gap is
easier to reason about.
2. margin: 0 auto centres a block, and only with a width.
.container {
max-width: 1100px;
margin-inline: auto; /* or margin: 0 auto */
}
auto means "take the remaining space", and with auto on both sides the
remainder splits evenly. Without a width or max-width the block already fills
its parent, there is no remaining space, and nothing happens. This is why "my
margin: 0 auto does not centre" is nearly always a missing width.
It also does not centre text (text-align does) and does not centre vertically
(Flexbox does).
3. Negative margins are legal.
.pull-up { margin-top: -20px; }
Useful occasionally — pulling a card over a banner. Mostly a sign you are fighting the layout, and in Flexbox or Grid there is almost always a better answer.
width, max-width, min-width
.card { width: 400px; } /* always 400, even at 320px viewport — overflows */
.card { max-width: 400px; } /* up to 400, shrinks below */
.card { min-width: 400px; } /* never below 400 — overflows on a phone */
Prefer max-width to width for anything that must survive a narrow screen.
width: 400px on a 360px phone gives horizontal scroll; max-width: 400px gives a
360px card.
min-width is the one that causes overflow you cannot find. A min-width on
something deep inside your layout forces its ancestors wider, and the scrollbar
appears on body far from the cause.
And the pair you will want constantly:
.container {
width: 100%;
max-width: 1100px;
margin-inline: auto;
padding-inline: 1rem; /* so text never touches a phone's edge */
}
That is the standard page container, and with border-box the padding is included
rather than making it 1132px.
Height is different
.box { height: 300px; }
Widths fill available space by default; heights fit their content. So setting a
fixed height is asking for trouble: put more text in than fits, and it spills out
of the box, over whatever is below.
Use min-height instead. The box is at least that tall and grows with its
content:
.hero { min-height: 60vh; }
100vh has a mobile problem worth knowing: on phones it historically meant the
viewport without the browser's address bar, so a 100vh section was taller than
the visible screen and the bottom was cut off. Use 100dvh — dynamic viewport
height — which accounts for the bar appearing and disappearing.
Overflow
.box {
overflow: visible; /* default — content spills out and is visible */
overflow: hidden; /* clipped */
overflow: auto; /* scrollbars only when needed */
overflow: scroll; /* always */
overflow-x: auto; /* one axis, as for the wide table in module 1 */
}
overflow: hidden is the tempting fix for content spilling out, and it hides the
problem rather than solving it — the content is still there, unreachable. Ask why
the box is too small first.
The one you will actually need: overflow-x: auto on a wrapper around a wide table,
as in module 1.
Finding the cause of a horizontal scrollbar
The most common frustrating layout bug, and there is a reliable procedure. In the console:
document.querySelectorAll("*").forEach(el => {
if (el.scrollWidth > document.documentElement.clientWidth) console.log(el);
});
That lists every element wider than the viewport. The culprit is nearly always an
image with no max-width: 100%, a fixed width in pixels, a min-width, a long
unbroken string, or 100vw on a page that has a vertical scrollbar — because
100vw includes the scrollbar's width and 100% does not.
display, briefly
display: block; /* full width available, stacks. div, p, h1 */
display: inline; /* flows in text, ignores width/height. span, a, em */
display: inline-block; /* flows in text, respects width/height and vertical padding */
display: none; /* removed entirely, no space kept */
The one to know now: width, height and vertical margins do nothing on an
inline element. Set width: 200px on a <span> and nothing happens, which is
confusing until you know. inline-block fixes it, and module 3 covers flex and
grid, which you will use far more.
And display: none versus visibility: hidden: the first removes the box and its
space; the second keeps the space and hides the contents. Both remove it from
screen readers.
Check your work
The four layers. Content, padding, border, margin — outward.
Padding versus margin. Padding is inside and the background covers it; margin is outside and always transparent.
Why width: 300px is not 300px. width sets the content box; padding and
border are added.
The one line that fixes it. box-sizing: border-box on * and both
pseudo-elements.
Shorthand order. Clockwise from top — TRouBLe.
What block and inline mean in logical properties. Down the page, and along
the line — which flips in Arabic or Urdu.
What collapsing margins do. Two vertical margins give the larger, not the sum.
Why margin: 0 auto sometimes does nothing. There is no width, so no remaining
space to split.
width versus max-width. Fixed and overflowing on a phone, versus shrinking.
Why min-width is dangerous. It forces ancestors wider and the scrollbar
appears far from the cause.
Why min-height beats height. Content that does not fit spills out of a fixed
height.
Why 100dvh rather than 100vh. 100vh ignores the phone's address bar.
Why overflow: hidden is not a fix. The content is still there and now
unreachable.
Why 100vw can cause horizontal scroll. It includes the scrollbar's width;
100% does not.
What does nothing on an inline element. width, height and vertical margins.
Practice
- Make a box
width: 300pxwith 20px padding and a 2px border. Measure it in devtools. Then addbox-sizing: border-boxand measure again. - Build three cards at
width: 33.33%with padding and watch them overflow. Fix withborder-box. - Give a card a background, then padding, then margin. Note where the background reaches.
- Write
padding: 10px 20px 30px 40pxand identify each side in devtools. - Replace it with
padding-blockandpadding-inline. - Set
direction: rtlon a container usingpadding-inline-startand watch it flip. - Put 30px bottom margin on one box and 20px top on the next. Measure the gap.
- Try
margin: 0 autowith no width. Then addmax-width. - Set
width: 400pxon a card and view at 360px. Change tomax-width. - Put
min-width: 500pxon a nested element and find the resulting horizontal scrollbar with the console snippet. - Set
height: 100pxon a box and put six lines of text in it. - Use
100vhthen100dvhfor a full-height section on a real phone. - Set
width: 200pxon a<span>. Thendisplay: inline-block. - Compare
display: noneandvisibility: hiddenon the same element, watching the space around it.
Official documentation
- MDN — Introduction to the CSS box model — The four layers, and content-box versus border-box.
- MDN — box-sizing — The one line that fixes
width. - MDN — Logical properties and values — The full
block/inlinemapping, and how it flips in right-to-left languages.
Next: how to see all of this in devtools instead of guessing.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship