RizTech Academy logo
RizTech Academy
CSS FoundationsLesson 4 of 830 min

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

  1. Make a box width: 300px with 20px padding and a 2px border. Measure it in devtools. Then add box-sizing: border-box and measure again.
  2. Build three cards at width: 33.33% with padding and watch them overflow. Fix with border-box.
  3. Give a card a background, then padding, then margin. Note where the background reaches.
  4. Write padding: 10px 20px 30px 40px and identify each side in devtools.
  5. Replace it with padding-block and padding-inline.
  6. Set direction: rtl on a container using padding-inline-start and watch it flip.
  7. Put 30px bottom margin on one box and 20px top on the next. Measure the gap.
  8. Try margin: 0 auto with no width. Then add max-width.
  9. Set width: 400px on a card and view at 360px. Change to max-width.
  10. Put min-width: 500px on a nested element and find the resulting horizontal scrollbar with the console snippet.
  11. Set height: 100px on a box and put six lines of text in it.
  12. Use 100vh then 100dvh for a full-height section on a real phone.
  13. Set width: 200px on a <span>. Then display: inline-block.
  14. Compare display: none and visibility: hidden on the same element, watching the space around it.

Official documentation

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