Lesson 12 / 22
Typographic Scale
Deriving size steps from a base and a ratio, how rounding distorts the neighboring-step ratio, computing the step budget, and placing interface levels onto scale steps.
Contents
The previous lesson tied the base size of body text to a typeface decision and computed that the same apparent size requires different nominal sizes across families. But the interface does not have a single size. The catalog interface carries at least five text levels: page title, record title, the borrow button’s text, author name, and metadata. What happens if these five numbers are each chosen individually?
What happens is this: the numbers are chosen independently, the ratios between them turn out random, and the 1.15 size-ratio threshold set in the Visual Hierarchy lesson is hit by chance in some pairs and missed in others. A scale is the structure that removes this chance: sizes are not chosen one by one, they are derived from a base and a ratio.
The Scale Is an Exponential Sequence
A typographic scale is a sequence of size steps that starts from a base size and is
multiplied or divided by a fixed ratio. The size of step n is written as:
Here is the base size, is the scale ratio, and is the step number. The base step is taken as zero; positive steps grow, negative steps shrink. The guarantee the scale gives is this: the ratio of any two neighboring steps is constant and equal to . Auditing the distinction comes down to looking at a single number.
This structure was introduced under the name modular scale in the Visual Presentation with CSS course; there, the problem was one of writing a declaration. Here, the problem is one of decision: which ratio, how many steps, and which level goes on which step.
Rounding Distorts the Fixed Ratio
The formula produces a continuous sequence; the screen, however, renders text more stably at integer pixel values. The difference looks negligible but directly distorts the neighboring-step ratio.
// scale.mjs — ratio-based typographic scale generation and rounding's effect on distinction const BASE = 16; // body text size (px) const STEPS = [-2, -1, 0, 1, 2, 3, 4]; const RATIOS = [1.125, 1.200, 1.250, 1.333]; const THRESHOLD = 1.15; // size-ratio threshold set in the Visual Hierarchy lesson function scale(ratio) { return STEPS.map((n) => { const raw = BASE * Math.pow(ratio, n); return { n, raw, rounded: Math.round(raw) }; }); } for (const ratio of RATIOS) { const s = scale(ratio); console.log(`\n--- ratio ${ratio.toFixed(3)} ---`); console.log("step raw (px) rounded neighbor ratio (raw) neighbor ratio (rnd) threshold"); for (let i = 0; i < s.length; i++) { const a = s[i]; if (i === 0) { console.log( `${String(a.n).padStart(4)} ${a.raw.toFixed(3).padStart(9)} ${String(a.rounded).padStart(8)}` + `${"-".padStart(23)}${"-".padStart(23)}${"-".padStart(11)}` ); continue; } const previous = s[i - 1]; const rawRatio = a.raw / previous.raw; const roundedRatio = a.rounded / previous.rounded; console.log( `${String(a.n).padStart(4)} ${a.raw.toFixed(3).padStart(9)} ${String(a.rounded).padStart(8)}` + `${rawRatio.toFixed(4).padStart(23)}${roundedRatio.toFixed(4).padStart(23)}` + `${(roundedRatio >= THRESHOLD ? "passed" : "FAILED").padStart(11)}` ); } }
--- ratio 1.125 --- step raw (px) rounded neighbor ratio (raw) neighbor ratio (rnd) threshold -2 12.642 13 - - - -1 14.222 14 1.1250 1.0769 FAILED 0 16.000 16 1.1250 1.1429 FAILED 1 18.000 18 1.1250 1.1250 FAILED 2 20.250 20 1.1250 1.1111 FAILED 3 22.781 23 1.1250 1.1500 passed 4 25.629 26 1.1250 1.1304 FAILED --- ratio 1.200 --- step raw (px) rounded neighbor ratio (raw) neighbor ratio (rnd) threshold -2 11.111 11 - - - -1 13.333 13 1.2000 1.1818 passed 0 16.000 16 1.2000 1.2308 passed 1 19.200 19 1.2000 1.1875 passed 2 23.040 23 1.2000 1.2105 passed 3 27.648 28 1.2000 1.2174 passed 4 33.178 33 1.2000 1.1786 passed --- ratio 1.250 --- step raw (px) rounded neighbor ratio (raw) neighbor ratio (rnd) threshold -2 10.240 10 - - - -1 12.800 13 1.2500 1.3000 passed 0 16.000 16 1.2500 1.2308 passed 1 20.000 20 1.2500 1.2500 passed 2 25.000 25 1.2500 1.2500 passed 3 31.250 31 1.2500 1.2400 passed 4 39.063 39 1.2500 1.2581 passed --- ratio 1.333 --- step raw (px) rounded neighbor ratio (raw) neighbor ratio (rnd) threshold -2 9.005 9 - - - -1 12.003 12 1.3330 1.3333 passed 0 16.000 16 1.3330 1.3333 passed 1 21.328 21 1.3330 1.3125 passed 2 28.430 28 1.3330 1.3333 passed 3 37.897 38 1.3330 1.3571 passed 4 50.517 51 1.3330 1.3421 passed
The first scale fails. Choosing a ratio of 1.125 means choosing a distinction below the threshold (1.15); rounding neither fixes this nor makes it worse, it only makes it irregular. The realized ratios oscillate between 1.0769 and 1.1500: one step pair grows by 7.7 percent, another by 15 percent — exactly double. The scale’s one guarantee — that the neighboring ratio stays constant — disappears along with the rounding.
The growth of the oscillation at small sizes is not a coincidence. The rounding error is at most half a pixel; this error’s effect on the ratio is inversely proportional to size. Half a pixel at 13 pixels is a 3.8 percent deviation; at 39 pixels it is a 1.3 percent deviation. The scale’s low end is three times as fragile as its high end.
In the other three scales, every rounded ratio clears the threshold. As the ratio grows, the oscillation’s effect shrinks proportionally: in the 1.333-ratio scale, the realized ratios fall between 1.3125 and 1.3571, meaning the widest ratio is 1.034 times the narrowest.
The rule that follows can be written this way: the scale ratio is chosen distinctly larger than the distinction threshold. A ratio equal or close to the threshold falls below it in some step pairs, depending on which direction the rounding happens to work.
The Step Budget
Growing the ratio has a cost. The size range the interface can use is bounded: readability sets the low end, the space it takes up on screen sets the high end. As the ratio grows, fewer steps fit into this range.
// steps.mjs — the scale's step budget and placing levels onto steps const BASE = 16; const SMALLEST = 12; // smallest allowed size for non-body text const LARGEST = 40; // largest heading size the catalog interface needs console.log("ratio step count (12-40 px range) first step last step"); for (const ratio of [1.125, 1.2, 1.25, 1.333, 1.5]) { const lowStep = Math.ceil(Math.log(SMALLEST / BASE) / Math.log(ratio)); const highStep = Math.floor(Math.log(LARGEST / BASE) / Math.log(ratio)); const count = highStep - lowStep + 1; const first = Math.round(BASE * Math.pow(ratio, lowStep)); const last = Math.round(BASE * Math.pow(ratio, highStep)); console.log( `${ratio.toFixed(3)} ${String(count).padStart(28)} ${String(first).padStart(12)} ${String(last).padStart(11)}` ); } // The catalog interface's levels, placed onto the steps of a 1.25-ratio scale. const RATIO = 1.25; const size = (n) => Math.round(BASE * Math.pow(RATIO, n)); const levels = [ { name: "page title", step: 3, weight: 700 }, { name: "record title", step: 1, weight: 600 }, { name: "button text", step: 0, weight: 600 }, { name: "author", step: 0, weight: 400 }, { name: "metadata", step: -1, weight: 400 }, ]; const THRESHOLD = { sizeRatio: 1.15, weightDiff: 200 }; console.log("\nlevel step size(px) weight"); for (const l of levels) { console.log( `${l.name.padEnd(15)} ${String(l.step).padStart(4)} ${String(size(l.step)).padStart(9)} ${String(l.weight).padStart(7)}` ); } console.log("\nneighboring level pair size ratio weight diff passing channel"); for (let i = 0; i < levels.length - 1; i++) { const upper = levels[i]; const lower = levels[i + 1]; const sizeRatio = size(upper.step) / size(lower.step); const weightDiff = upper.weight - lower.weight; const passing = []; if (sizeRatio >= THRESHOLD.sizeRatio) passing.push("size"); if (weightDiff >= THRESHOLD.weightDiff) passing.push("weight"); const label = `${upper.name} > ${lower.name}`; console.log( `${label.padEnd(32)} ${sizeRatio.toFixed(3).padStart(10)} ${String(weightDiff).padStart(13)} ${passing.length ? passing.join("+") : "WEAK"}` ); }
ratio step count (12-40 px range) first step last step 1.125 10 13 36 1.200 7 13 40 1.250 6 13 39 1.333 5 12 38 1.500 3 16 36 level step size(px) weight page title 3 31 700 record title 1 20 600 button text 0 16 600 author 0 16 400 metadata -1 13 400 neighboring level pair size ratio weight diff passing channel page title > record title 1.550 100 size record title > button text 1.250 0 size button text > author 1.000 200 weight author > metadata 1.231 0 size
The first table shows the real trade-off in choosing a scale ratio. The same 12–40 pixel range fits ten steps at a ratio of 1.125, three steps at 1.5. The previous computation showed that a 1.125 ratio cannot clear the threshold; this table shows that a 1.5 ratio leaves room for only three levels. The catalog interface needs five levels, so a 1.5 ratio is eliminated outright. The remaining candidates are 1.2, 1.25, and 1.333; all clear the threshold and all fit five levels.
The decision was made in favor of a 1.25 ratio. The rationale is the slack in the second table: six steps leave a one-step margin for five levels. A scale that is completely full means that adding a level later requires recomputing the whole scale.
Not Every Level Needs a New Step
Two points stand out in the third table’s level placement.
First, button text and author name sit at the same step: both step zero, 16 pixels. The size ratio is 1.000; the size channel produces no distinction. The distinction is built in weight, and the 200-unit difference clears the threshold. The two-channel rule from the Visual Hierarchy lesson is met here by a third channel: button text sits on a filled surface, author name on a plain background. Surface is a channel that does not appear in this table but is genuinely at work, and it is the subject of the Color System lesson.
Second, the comparison in the fourth row is not actually a hierarchical pair. Button text and author name do not compete in the same visual area; one is in the action zone, the other inside the record row. The neighbor audit treats the levels as a sorted list, but hierarchy is meaningful only among elements seen at the same time. Because the table produced no warning here, this distinction caused no problem, but whether a row marked WEAK is a real problem is always checked with this same question.
The last row closes out a finding from the first lesson. There, the distinction between author and metadata stood only in the contrast channel and was counted as one-legged. The scale, by lowering metadata to 13 pixels instead of 14, raised the size ratio to 1.231; the distinction now stands in both size and contrast. The step decision solved, through the scale’s own structure, a problem that choosing sizes one by one could not.
The Scale’s Written Form
If the scale remains a computation, it gets redone and drifts at every use. Its written form is the conversion of steps into named values:
:root { --size--1: 13px; --size-0: 16px; --size-1: 20px; --size-2: 25px; --size-3: 31px; }
Names carrying the step number are more durable than names carrying meaning like “heading” or “small.” Meaning-bearing names start lying when the interface changes: a value named “heading” one day gets used somewhere that is not a heading. A step number, by contrast, only states its position in the scale, and that position does not change.
This list has no intermediate value, and it should not. When the feeling arises that a level needs 18 pixels, the answer is not to add a new variable but to decide which step the level belongs to. Every number that steps outside the scale cancels the scale’s guarantee at that point.
Summary
- A typographic scale derives sizes exponentially from a base and a ratio; the guarantee it provides is that the neighboring-step ratio stays constant.
- Rounding to integer pixels breaks this constancy; the deviation’s effect on the ratio is larger at small sizes, so the scale’s low end is its most fragile point.
- The scale ratio is chosen not equal to the distinction threshold but distinctly larger than it; a ratio close to the threshold drops below it in some step pairs through rounding.
- As the ratio grows, fewer steps fit into a given size range; choosing a ratio is a trade-off between distinguishing power and step budget.
- Not every level needs a new step; two levels at the same step can be separated in the weight or surface channel.
- The scale is written as named step values; adding an intermediate value cancels the scale’s guarantee at that point.
Next Step
The scale said how large the text would be, not how wide. When the summary paragraph in a record detail spreads across the full column, lines grow long, and the eye starts missing its target as it returns from the end of one line to the start of the next. The next lesson computes line length in characters, ties the measure to column width, and shows why leading has to be adjusted together with line length.
To keep your progress and take notes, Log in
My notes
Log in to take notes.