SIGNAL · WEB

Red Blob Games Responsive Design

Model layout as a function from browser width to content width, and demand it be monotonic (wider browser never narrows content) and continuous (no jumps) — then implement it as a piecewise linear function in CSS (calc() with a vw slope term, breakpoints only where segments meet).

5 MIN

TLDR

  • Model layout as a function from browser width to content width, and demand it be monotonic (wider browser never narrows content) and continuous (no jumps) — then implement it as a piecewise linear function in CSS (calc() with a vw slope term, breakpoints only where segments meet).
  • Scale font size with the same linear-interpolation trick so line length stays ~66–75 characters as paragraphs widen; but keep px font sizes inside scaled diagrams or scaling applies twice.
  • Each embedded element type needs its own responsive recipe: svg via viewBox, img via max-width:100%, canvas via width:100% + devicePixelRatio rescale, flexbox for side-by-side; iframes and absolute positioning largely resist conversion.

Caveman

Content width is a function of browser width. Draw it.

ELI5

Instead of asking “what should my page look like on a phone vs. a tablet vs. a laptop?”, ask “as the window gets wider, how should the text column grow?” Draw that as a simple graph: at small sizes the text takes almost everything, in the middle it shares new space with the margins, and past a cap the margins absorb all growth. Then translate each straight segment of that graph directly into a CSS calc() rule.

Trunk → Branches

Trunk: Design responsive layout as a monotonic, continuous function from browser width to content width — device-independent math, not device-specific breakpoints. Branches:

  • Fixed widths fail across device classes: one 600px-wide “scale to fit” layout made text too small on phones, too big on tablets, and paragraphs too narrow on desktops (§ intro).
  • Visualize the function first: plotting browser width → layout exposed that even a beautiful, admired site was responsive but not monotonic — paragraphs got narrower going from 767→768px (§2).
  • Piecewise linear content width: three segments — calc(100vw - 36px) (slope 1, phones), calc(330px + 33vw) (slope ⅓, mid), constant 660px past 1000px (slope 0) — where the vw coefficient is the slope (§3).
  • Font size interpolates too: calc(10px + 1vw) capped at 20px keeps the paragraph-width÷font-size ratio ≈33 (~66 chars/line), derived by picking two good endpoints and lerping between them (§4).
  • Two-column layouts are the same math with more segments: either grow both columns equally, or fill the text column first then grow the diagram column — each strategy is just two more breakpoints computed from the column-width sums (§6).
  • Per-element conversion is manual: old content doesn’t declare whether an element should shrink-to-fit or match paragraph width, so pages must be converted one at a time; interactive elements additionally need mouse-coordinate rescaling (§7).
  • Verify with screenshot diffing: headless Firefox/Chrome screenshots at several widths, before vs. after, on a quick self-built comparison page (§8).

Key Patterns

  • Pattern: Expressing a design constraint as a function with required properties (monotonic, continuous) works because it turns aesthetic judgment into checkable math — transfers to any parametric design problem (spacing scales, type ramps, chart sizing).
  • Pattern: Linear interpolation between two hand-picked good endpoints (16px@600w, 20px@1000w → 10px + 1vw) works because you only need taste at the extremes and math fills the middle — transfers to fluid typography (aka “CSS locks”) and animation easing anchors.
  • Pattern: Breakpoints derived from content arithmetic (margin + column + gutter sums like 1110px, 1374px) work because layout switches exactly when content genuinely fits — transfers anywhere over device-specific breakpoints (768px iPad-ism).

Stress Test

  • Hidden assumption: the page is a single centered content column of flowing text + figures. The whole function-of-width model presumes one primary axis to allocate.
  • Best counterargument: almost nobody resizes their browser continuously — the author admits users won’t notice non-monotonic jumps. Standard device-bucket breakpoints (the approach the admired site used) are simpler to reason about, better supported by frameworks, and produce indistinguishable results for real visitors.
  • Fails when: layouts use iframes (CSS won’t preserve aspect ratio or resize contents) or complex absolute positioning — the author gave up and left the visibility page at fixed 600px. Also strains app-like UIs (dashboards, multi-pane tools) with no single content column.
  • Fact vs opinion: “readable paragraphs are ≤75 characters” is a typography guideline, not established fact. Preferring monotonic/continuous over device breakpoints is the author’s stated design taste — he concedes most users would never notice the difference; likewise “not convinced” the in-CSS calculator beats precomputed values (§5).

In Practice

  • Set content width as a piecewise linear function: --body-width: calc(100vw - 36px), then calc(330px + 33vw) past a 550px breakpoint, then constant 660px past 1000px — the vw coefficient is the slope of each segment — [§3]
  • Scale fonts the same way: --font-size: calc(10px + 1vw) capped at 20px; derive constants by choosing target sizes at two widths and applying the lerp formula; keep fixed px fonts inside scaling diagrams to avoid double-scaling — [§4]
  • Modern simplification (as of ~2020+): replace breakpoint pairs with min()/max()/clamp(); the author’s 2026 revision computes the whole layout in CSS custom properties with min() and round(down, …, 1px). Units: rem honors the user’s global default-font-size, px forces per-site zoom — he later moved to rem for accessibility, and regrets non-round maxes (41.25rem; 45/50rem would give 720/800px images) — [§5, §9]
  • Per-element responsive recipes: svg → viewBox instead of width/height (+ max-width to shrink-but-not-grow, wrapped in <figure> for margin:auto centering); img → max-width:100%; height:auto; canvas → width:100% in CSS while multiplying mouse coords by canvas.width / canvas.offsetWidth and sizing the backing store by devicePixelRatio; side-by-side → flexbox with flex-wrap + flex-basis: calc(var(--body-width)/2) — [§7]
  • Two-column breakpoints come from content sums: e.g. margins + 2×528px columns + gutters = 1110px to start two columns; = 1374px when both hit 660px max (equal-growth strategy), or start the diagram column at 400px only after the text column is full width (text-priority strategy) — [§6]
  • Regression-test visually: headless firefox -screenshot --window-size=$width,4000 "$url" / chrome --headless --screenshot at several widths, diff before/after on a comparison page — [§8]

Connections

  • ASCII Text Images in p5.js — a browser figure with fixed-px inner type — that note renders ASCII as monospace DOM text tuned by line-height; this note’s rule to keep px fonts inside scaled diagrams (scale the container, not the glyphs) is what keeps such a figure aligned on a fluid page.

navigate · open · esc close