Constraint-Based Width: The "Auto" Value Puzzle
Building a Web Browser Engine: Layout & Rendering
Chapter 4 · Constraint-Based Width: The "Auto" Value Puzzle
Chapter 3 handled exactly one case: width: auto, with margins that were never anything but a plain number. Real CSS allows three independent things to be auto at once — width, margin-left, and margin-right — and which one (or two) actually end up auto changes which equation the browser has to solve. This chapter builds the real algorithm.
A Bug Inherited Directly From Chapter 3's Own Approach
Chapter 3 computed margin the same way it computed padding and border-width — expand_shorthand(), straight from Chapter 2. That function calls parse_length() on every token in a shorthand value, unconditionally.
margin: 0 auto — the classic "center this block horizontally" trick, quite possibly the most-copy-pasted line of CSS ever written — fed through Chapter 3's own margin handling raises ValueError: unsupported length: 'auto'. expand_shorthand() was built for padding and border-width, two properties that genuinely never allow auto at all — parse_length('auto') has no case for that string, and never needed one, until margin (which does allow auto) started reusing the same function.
expand_margin_shorthand('0 auto') returns top=0.0, bottom=0.0, left='auto', right='auto' — the 2-value shorthand rule (vertical, horizontal) applying exactly as before, just with the horizontal component preserved as the literal string 'auto' rather than an attempted (and failing) number parse.
The Real Algorithm: Three Cases
margin-left + border-left + padding-left + width + padding-right + border-right + margin-right has to equal the containing block's own width. Solve for whichever piece(s) are auto.
padding: 10px, no margin set (defaults to a plain, non-auto 0). Content width: 280.0 — identical to Chapter 3's own result. This chapter's algorithm is a strict generalization, not a replacement.
width: 100px; margin: 20px; (both sides, explicitly 20px each) in that same 300px container. Final margin.left: 20.0, exactly as declared. Final margin.right: 180.0 — not the declared 20px. This isn't a bug in this engine; it's a real, specified rule: when width and both margins are all non-auto, the total is over-constrained, and (in LTR) the browser silently recomputes margin-right to make everything fit exactly, discarding whatever value was actually written for it. Real CSS authors get caught out by this regularly.
width: 100px; margin: 0 auto;. Final margin.left: 100.0. Final margin.right: 100.0. 100 + 100 + 100 = 300 — the box sits exactly centered, because both margins being auto means the leftover space (containing block width − everything else) is split evenly between them.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
A dedicated MarginEdges/expand_margin_shorthand, distinct from Chapter 2's own EdgeSizes/expand_shorthand | Chapter 2's own established pattern of one shared mechanism (expand_rect_by) driving all three outer boxes — this chapter shows that pattern has a real limit: margin's own auto behavior is genuinely different from padding/border-width, and reusing identical code for all three was a mistake worth catching explicitly |
| The over-constrained case silently discarding a declared margin-right value | A real, well-known CSS gotcha independent of this course entirely — worth knowing when debugging a real page where an author's own margin value seems to be "ignored" by the browser for no apparent reason |
| Both-auto margins splitting the leftover space evenly | Chapter 3's own calculate_block_position, unchanged — once dimensions.margin.left holds a real, resolved number (never the string 'auto'), position calculation works exactly as already built, with no changes needed there at all |
Hands-On Exercises
Build a box with width: 100px; margin: 0 20px 0 auto; — a 4-value shorthand where only left is auto — inside a 300px containing block. Compute margin.left and margin.right and confirm the explicitly-set margin-right (20px) is left completely untouched while margin-left alone absorbs the entire remaining space.
Build a box with width: 250px; margin: 10px auto; inside a 400px containing block. Confirm the box centers correctly (both horizontal margins equal, and their sum plus the width equals the container's own width), and confirm the shorthand's own vertical component (10px) is completely unaffected by the auto-margin solving.
Reproduce this chapter's own crash directly: call calculate_block_width_naive() on a box with margin: 0 auto and confirm it raises ValueError. Identify the exact function call inside calculate_block_width_naive responsible, and explain specifically why padding and border-width never trigger this same crash even though they go through structurally identical shorthand-parsing code.
Chapter 4 Quick Reference
- Real bug found and fixed: reusing Chapter 2's own general-purpose shorthand expander for margin crashes on
auto— margin needs its ownMarginEdges/expand_margin_shorthand, since it's the only one of the three (margin/border/padding) that CSS allowsautoon at all - width:auto (margins fixed) — width absorbs all remaining space, exactly matching Chapter 3's own already-verified behavior
- Neither margin auto, explicit width — over-constrained (or an exact fit): margin-right is silently recomputed to fill the leftover space, its own declared value discarded (a real, specified CSS rule, not a bug)
- One margin auto — that margin alone absorbs all remaining space
- Both margins auto — the classic centering idiom: leftover space is split evenly between left and right, verified numerically centered
- Next chapter: Inline Layout & Line Boxes — giving real width and height to the text and inline content this course has deferred since Chapter 1