The Box Model: Content, Padding, Border, Margin
Building a Web Browser Engine: Layout & Rendering
Chapter 2 · The Box Model: Content, Padding, Border, Margin
Every real element on a page occupies four nested rectangles, not one: a content box, wrapped by padding, wrapped by a border, wrapped by margin. Every later chapter in this course computes real pixel positions by growing one of these boxes outward into the next. Getting the arithmetic and the CSS shorthand parsing genuinely right here is what everything downstream depends on.
box-sizing: content-box — the default, where an element's own width/height describe the content box specifically, and padding/border/margin all add on top of that. box-sizing: border-box (where width instead describes the outer, padding-and-border-inclusive box) is a real, common CSS feature — deliberately out of this course's own stated scope.
Four Boxes, Grown Outward One at a Time
Every one of the three outer boxes is defined purely in terms of the box directly inside it — the same one expand_rect_by function, called three times in sequence, is the entire box model. This is exactly what a real browser's own DevTools "box model" panel visualizes: content, padding, border, and margin as four nested rectangles, each one measurably bigger than the last.
Bug 1: A Length Parser That Crashes on CSS's Own Valid Zero
parse_length_naive('16px') works fine — 16.0. But parse_length_naive('0') raises ValueError: could not convert string to float: '' — '0'[:-2] on a one-character string is the empty string, and float('') fails. CSS explicitly allows a bare, unitless 0 (every other length needs a unit, but zero never does) — and Chapter 9's own published UA_STYLESHEET already contains exactly this shape: Rule(['p'], [Declaration('margin', '16px 0')]). Parsing that real, already-existing rule's own value with the naive parser crashes immediately.
parse_length('16px 0'.split()) — parsing each token of Chapter 9's own real margin value separately — gives [16.0, 0.0], correctly.
Bug 2: Shorthand Expansion Getting the 3-Value Form Wrong
CSS's own margin/padding/border-width shorthand accepts 1, 2, 3, or 4 space-separated values, each shape meaning something genuinely different.
t, r, b = lengths; l = t — reaches for the top value as left's own fallback, since it's the first one already in scope. expand_shorthand_naive("10px 20px 30px") gives EdgeSizes(top=10, right=20, bottom=30, left=10). Real CSS's 3-value form means (top, horizontal, bottom) — left is supposed to reuse the horizontal (second) value, 20, matching right, not the top value. The fixed version's l = r gets this right: EdgeSizes(top=10, right=20, bottom=30, left=20).
expand_shorthand("5px") → all four sides equal, 5 each. expand_shorthand("10px 20px") → vertical 10, horizontal 20. expand_shorthand("1px 2px 3px 4px") → top/right/bottom/left exactly as written, clockwise from the top.
A Real Dimensions Computation, Box by Box
A content box 100×50 at the origin, padding: 10px, border-width: 2px, margin: 16px 0 — the exact margin shape from Chapter 9's own real UA rule.
Rect(0, 0, 100, 50). Padding box: 10px on all sides → Rect(-10, -10, 120, 70) (width/height each grow by 20 — 10 on both sides). Border box: a further 2px all sides → Rect(-12, -12, 124, 74). Margin box: 16px 0 — a 2-value shorthand, vertical 16, horizontal 0 — adds 16 to top and bottom only → Rect(-12, -28, 124, 106), width unchanged from the border box, height up by 32.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
One expand_rect_by function driving all three outer boxes | Chapter 3's own block layout, which will use margin_box()/border_box() directly to decide how much vertical space a block actually occupies and where the next sibling starts |
| A genuinely real bug (bare unitless zero) traced directly to already-published Chapter 9 code | Chapter 10's own capstone integration bug, from Course 1 — another case where a value produced correctly by an earlier chapter exposed a genuine gap only once a later chapter actually tried to consume it |
| CSS shorthand's own real, specific value-count rules | A recurring theme across this whole site's CSS-adjacent material: shorthand properties look simple but encode real, easy-to-misremember rules — getting the 3-value case backwards is a mistake real CSS authors make too, not just an engine bug |
Hands-On Exercises
Call expand_shorthand() on "4px 8px 12px 16px" (a full 4-value form), "3px 6px" (2-value), and "20px" (1-value). Confirm each produces the exact EdgeSizes real CSS's own shorthand rules predict, and state which one of the three forms is the only one where all four sides can end up genuinely different from each other.
Build a Dimensions for a content box 200×100 at the origin, with padding: '4px 8px 12px 16px', border-width: '3px 6px', and margin: '20px'. Compute padding_box() and border_box() and confirm both against hand-worked arithmetic, showing your work for each edge separately rather than only checking the final numbers.
Reproduce this chapter's own two bugs directly: call parse_length_naive('0') and confirm it raises ValueError, then call expand_shorthand_naive("10px 20px 30px") and confirm its own left value is wrong. For each bug, identify the exact line responsible and explain, in your own words, why the mistake is easy to make even though it's wrong.
Chapter 2 Quick Reference
- Four boxes: content → padding → border → margin, each one the box before it grown outward by a real
EdgeSizes - Scope:
box-sizing: content-boxonly —width/heightdescribe the content box;border-boxis out of scope - Real bug found and fixed: a naive length parser assuming every value ends in
'px'crashes on CSS's own valid bare zero — already present in Chapter 9's own published UA stylesheet (margin: 16px 0) - Real bug found and fixed: naive 3-value shorthand expansion reuses the top value for
leftinstead of the correct horizontal (second) value - Verified: all four shorthand forms (1/2/3/4 values) match real CSS's own defined expansion rules exactly
- Next chapter: Block Layout — using these box dimensions to actually stack a block box's own children vertically and compute real widths and heights