CSE 134B Web Client Languages
  1. Presentation
  2. Fonts
  3. Size, spacing and measure

Presentation · Fonts 06

Size, spacing and measure

A good typeface can be set badly. Too small, too tightly spaced, or run across a line so long the eye loses its place, and the best letters in the world stop being read. This page is about the numbers around the letters: how big, how far apart, how long a line, and which CSS units let those numbers follow the reader instead of fighting them.

The CSS section already teaches the unit system in Values and units, clamp() in its functions and fluid design, and zoom and reflow in CSS for people. This page does not repeat them. It asks a narrower question: which of those units suit type, and why. It sets the old ideas of leading, tracking and measure in CSS, and checks each choice against two readers: one who has asked for larger text, and one who has asked for more space between the lines.

1. Start from the reader's size

Every browser has a default font size, and every major browser ships it at 16 pixels. Readers can change it: Chrome's appearance settings offer Very small, Small, Medium, Large and Very large, which are 9, 12, 16, 20 and 24 pixels; Firefox has the same setting, and Safari offers page zoom and a minimum font size. A reader who has chosen Large has told every website, once, that 16 pixels is too small for them. Chrome also has a separate minimum font size, off by default, which enlarges any smaller text up to that minimum.

The default is the one fact about the reader a style sheet can respect without knowing it. rem is the unit that does: 1rem is the root element's font size, which is the reader's default unless the page overrides it. Body text at 1rem is exactly what the reader asked for; a heading at 2rem is twice that, for every reader. Pixels do not respond to the setting at all, as Values and units explains: body text at 16px is 16 pixels for the reader who chose 24.

html { font-size: 16px; } /* every rem on the page now ignores the setting */ body { font-size: 14px; } /* and the body text ignores it twice */ @layer base { /* No font-size on html: 1rem is the reader's default. */ body { font-size: 1rem; } small, figcaption { font-size: 0.875rem; } h1 { font-size: 2rem; } }

Page zoom scales pixels and rems alike, which is why pixel-sized pages pass a quick zoom test and still fail readers who set a larger default once and expect every site to honor it. rem serves both.

The 62.5% trick

A long-lived habit sets the root to 62.5%, so that 1rem is 10 pixels at the default size and 1.4rem reads as "14 pixels":

html { font-size: 62.5%; } /* 10 pixels when the default is 16 */ body { font-size: 1.6rem; } /* back to the reader's default */ h1 { font-size: 3.2rem; }

To be fair to it: a percentage is relative, so this still respects the reader. With a 20-pixel default, 1rem is 12.5 pixels and the body is 20, so nothing is lost. The costs are elsewhere. Every rule must remember to scale back up, and anything that forgets, such as a form control or a quick fix, is set at 10 pixels. Third-party components and design-system packages written for 1rem = 16px render 37.5% too small, a problem Josh Comeau describes in "The Surprising Truth About Pixels and Accessibility". And the trick exists to make pixel arithmetic easy, which keeps the team thinking in pixels. A scale of named sizes (section 5) removes the arithmetic without changing the root.

2. Which unit for which job

Typography needs several kinds of length, and each has a natural unit, chosen by asking what the length should follow when something changes: the reader's setting, the size of the text beside it, or the width of the line.

Units for type, by what they should follow
LengthUnitWhy
font-sizeremFollows the reader's default, and gives one fixed step on the page's scale wherever the element sits
Space tied to the text: button padding, the gap before an icon, letter and word spacingemGrows with the text it belongs to, so a small button and a large one keep the same proportions
line-heighta number, no unitInherits as a ratio, so every element computes its own leading from its own size (section 3)
Line lengthchCounts, roughly, characters, and follows the font and its size (section 4)
Space between blocks, in lineslh, rlhOne line of the element's text, or of the root's
Things matched to letters: icons, underlinescap, ex, emCap height, x-height or the em itself, read from the font (Anatomy and vocabulary)
Hairline rules and borderspxA 1-pixel line should stay a hairline

The font-relative units all depend on the font, which is the point. Anatomy and vocabulary showed that font-size sets the em, not the height of any letter, so two fonts at the same size can differ a good deal in how big they look; ex and cap measure what the eye actually sees, and ic, the width of the CJK ideograph 水, gives one full-width character for Chinese and Japanese text.

The em compounding trap

em means the element's own font size, except in font-size itself, where it means the parent's. That one exception is where trouble starts. A size in em multiplies the size it inherits, and nested elements multiply again:

li { font-size: 0.9em; } /* level 1: 0.9 × 16 = 14.4 pixels level 2: 0.9 × 14.4 = 12.96 level 3: 0.9 × 12.96 = 11.66, and falling */

The same rule in rem gives every list item the same size at any depth. The habit that follows: sizes in rem, spacing in em. Padding in em never compounds, because padding does not inherit; it is simply computed from the element's own font size, which is exactly what makes a button scale cleanly:

@layer components { button { font-size: 1rem; padding: 0.5em 1em; /* half and one times this button's text */ border-radius: 0.375em; } button[data-size="small"] { font-size: 0.875rem; } /* padding follows */ button svg { block-size: 1cap; /* as tall as the capitals in the label */ margin-inline-end: 0.4em; } }

3. Line height: unitless, and why

Anatomy and vocabulary introduced leading, named for the strips of lead a compositor put between lines of metal type. CSS calls it line-height, and it is not quite the same thing. Leading in metal was the space added between lines; CSS's line-height is the whole height of each line, baseline to baseline when the lines are alike. The browser takes the font's own height, its ascent plus descent from the font's tables, subtracts it from the line height, and splits the difference: half above the text, half below.

How a line box is built from line-heightTwo lines of 48-pixel text with line-height 1.5, so each line box is 72 pixels tall. Inside each line box, the content area is the font's ascent plus descent, here 1.2 em or 57.6 pixels, and the remaining 14.4 pixels of leading is split into two half-leadings of 7.2 pixels, one above and one below the content area. A dashed baseline runs through each line. An arrow on the right measures 72 pixels from one baseline to the next. Below, the arithmetic, and a note that with line-height 1 the half-leading becomes negative and lines can overlap.Type sitson linesbaseline to baseline:line-height 1.5 = 72pxhalf-leading, 7.2pxcontent area: the font'sascent + descent, 57.6pxtwo half-leadings, 7.2px eachbaselineline box: 72pxfont-size 48px · line-height 1.5 → line box 72pxleading = 72 − 57.6 = 14.4px → 7.2px above, 7.2px belowWith line-height: 1, the line box (48px) is smaller than the content area (57.6px):the half-leading is negative, and tall letters on adjacent lines can touch.
A line box is the font's own height plus leading, split half above and half below. The ascent and descent come from the font, so the same line-height places text slightly differently in different fonts.

The value normal leaves the line height to the font's metrics. CSS 2.2 recommends a value between 1.0 and 1.2, and real fonts land all over that range and beyond, so normal changes when the font changes, including when a fallback is swapped for a web font. Web fonts and performance found that a numeric line height made matched fallbacks far more robust for exactly that reason.

Why the number has no unit

line-height accepts a number, a length or a percentage, and they inherit differently. A length or percentage is turned into pixels on the element where it is declared, and those pixels are inherited. A number is inherited as a number, and each descendant multiplies it by its own font size. Measured in Chrome: a section with font-size: 16px and line-height: 1.5em computes 24 pixels, and an h2 inside it at 32 pixels inherits 24-pixel lines, shorter than its own text. The same section with line-height: 1.5 gives the heading 48-pixel lines.

article { font-size: 1rem; line-height: 1.5em; } /* 24 pixels, inherited as 24 */ article h2 { font-size: 2rem; } /* 32-pixel text on 24-pixel lines */ @layer base { body { line-height: 1.5; } h1, h2, h3 { line-height: 1.15; } /* big type needs less */ small, figcaption { line-height: 1.45; } }

The tuning follows a pattern old in print and still true on screen: larger text needs proportionally less leading. Body text suits 1.5 to 1.65; headings look disconnected above about 1.2. The right values depend on the face, since a large x-height fills more of the line, so set them per role in the type system rather than once for everything.

Measuring in lines: lh and rlh

Two newer units measure lines directly. 1lh is the element's computed line height, and 1rlh is the root element's. Space in lh is space in lines of the text beside it: a paragraph gap of 1lh is one blank line, and a pull quote with margin-block: 1rlh leaves exactly one line of body text above and below. They are supported in Chrome 109 and 111, Firefox 120 and Safari 16.4, and are Baseline widely available. When the line height is normal, lh uses the metrics of the first available font, so the unit is most predictable with a numeric line height.

4. Measure: the length of a line

Typographers call the length of a line its measure. A line that is too long makes the eye travel far to the right and then hunt for the start of the next line; a line that is too short breaks phrases across lines and spends the reader's attention on line returns. The most quoted guidance is Robert Bringhurst's, in The Elements of Typographic Style:

Anything from 45 to 75 characters is widely regarded as a satisfactory length of line for a single-column page set in a serifed text face in a text size. The 66-character line (counting both letters and spaces) is widely regarded as ideal. For multiple column work, a better average is 40 to 50 characters.

Note the hedges: "widely regarded", a single column, a serif face, a text size. It summarizes print practice, and the research on screens is more mixed. Mary Dyson's 2004 review of reading from screens concludes that "although longer line lengths may be read faster, people prefer a more moderate length". Dyson and Haselgrove (2001) found better comprehension at 55 characters per line than at 100. Shaikh and Chaparro (2005) tested 35, 55, 75 and 95 characters and found the longest read fastest with comprehension unchanged, though readers had strong opinions about the extremes. Nanavati and Bias (2005), reviewing the literature, recommend not exceeding about 70. In short: long lines are not slow on screen, but moderate ones are preferred and may be understood better, so 45 to 75 characters remains a sound default for text meant to be read.

Setting a measure in ch

ch is the advance width of the digit zero in the element's font (half an em, says CSS Values 4, if it cannot be measured). So max-inline-size: 65ch is "the width of sixty-five zeros", and it follows the font and its size, which a measure in pixels or rem does not. But zero is not an average character. In most proportional fonts digits are wide, built to line up in tables, and lowercase letters and spaces are narrower, so a line of N ch holds more than N characters of text. Measured in Chrome with the lab below:

Characters per line actually set, by measure and font (average of full lines, 18-pixel text)
Font45ch55ch65ch75ch
Literata (serif)54678090
Inter (sans)59728799
Atkinson Hyperlegible Next668097110
Recursive, monospaced44546373
What max-inline-size: 65ch holds in four fontsA bar chart of characters per line, from 0 to 110, with a shaded band from 45 to 75 characters. At a measure of 65ch, Literata sets 80 characters per line, Inter 87, Atkinson Hyperlegible Next 97, all beyond the band, and Recursive in its monospaced form 63, inside it. Only in the monospaced font is one ch one character.45–75 charactersLiterataInterAtkinson Hyp. NextRecursive Mono8087976304575100
All four paragraphs are max-inline-size: 65ch. In proportional fonts the zero is wider than the average character, spaces included, so the line holds 20 to 50 percent more characters than its ch value.

The common advice "use 65ch" therefore yields 80 to 97 characters in these proportional faces, past the top of Bringhurst's range. For a line of about 66 characters, these fonts want roughly 45ch to 55ch. Only in a monospaced font, where every glyph is as wide as the zero, does one ch equal one character. Treat ch as a unit that scales correctly, and pick its number by measuring the font you actually use; the fallback will give a different count.

@layer layout { :root { --measure: 52ch; } /* about 66 characters of Literata */ article { max-inline-size: var(--measure); margin-inline: auto; padding-inline: 1rem; } }

5. Type scales and modular ratios

A page needs a handful of sizes: body text, one or two smaller sizes for captions and notes, and a few larger ones for headings. Chosen one at a time, they drift: 15 pixels here, 17 there, a 22 that is almost but not quite the 24 used elsewhere. A type scale chooses them together, usually by multiplying a base size by one ratio again and again. Each step is the previous one times the ratio, so the sizes relate the way musical intervals do, and the common ratios carry musical names:

Common scale ratios, and the sizes they give from 1rem
RatioNameSteps up from 1remCharacter
1.125Major second1.125, 1.266, 1.424, 1.602Subtle; dense interfaces
1.2Minor third1.2, 1.44, 1.728, 2.074Gentle hierarchy
1.25Major third1.25, 1.563, 1.953, 2.441Balanced; a safe default
1.333Perfect fourth1.333, 1.777, 2.369, 3.157Distinct steps; editorial
1.5Perfect fifth1.5, 2.25, 3.375, 5.063Strong contrast; few steps fit

CSS can now do the arithmetic itself with pow(), so the ratio is one custom property and changing the whole scale is a one-line edit:

@layer tokens { :root { --ratio: 1.25; --step--1: calc(1rem / var(--ratio)); /* 0.8rem */ --step-0: 1rem; --step-1: calc(1rem * var(--ratio)); /* 1.25rem */ --step-2: calc(1rem * pow(var(--ratio), 2)); /* 1.563rem */ --step-3: calc(1rem * pow(var(--ratio), 3)); /* 1.953rem */ --step-4: calc(1rem * pow(var(--ratio), 4)); /* 2.441rem */ } }

A scale is a tool, not a law. Its value is that it limits choice: five to eight sizes, related to each other, used everywhere, so that a heading is recognizably a heading by its size alone. Nothing about the golden ratio makes a page more readable, and a scale that produces a 0.64rem caption should lose that step rather than keep it for the sake of the math. Designers round, skip and nudge steps, and a small screen may want a smaller ratio than a large one. The scale belongs to the typographic system, declared once as tokens, and size is only one way to build hierarchy.

6. Fluid type and the zoom trap

Fluid type makes size a function of the viewport width, so a heading suits both a phone and a wide monitor. Fluid design shows the standard form: clamp(MIN, PREFERRED, MAX), such as clamp(1.5rem, 1rem + 2vw, 3rem), which rises in a straight line between its bounds.

The tempting shortcut, font-size: 4vw alone, fails a reader who zooms, and here is exactly why. Browser zoom makes every CSS pixel larger on screen. A window 1,280 pixels wide holds 1,280 CSS pixels at 100%, but only 640 at 200%, each drawn twice as large. 1vw is one percent of the viewport in CSS pixels, so at 200% it halves: 4vw goes from 51.2 CSS pixels to 25.6, and drawn twice as large, 25.6 CSS pixels are the same 51.2 pixels on screen as before. Text sized only in vw stays a fixed fraction of the window, however far the reader zooms. It ignores the default font size too, since nothing in 4vw refers to it.

h1 { font-size: 4vw; } /* the same size on screen at 100%, 200% and 500% zoom */

WCAG 2.2 success criterion 1.4.4, Resize Text, at level AA, says: "Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality." The W3C lists this exact mistake as failure F94, "Failure of Success Criterion 1.4.4 due to incorrect use of viewport units to resize text."

The fix is a rem term, and it is only part of the fix

A rem term in the preferred value, and rem bounds, bring the reader back: the rem parts grow with zoom and with the default size. But the vw part still does not, so a fluid size grows by less than the zoom. The figures below were measured in Chrome 156 by emulating a window of a given width with a device scale factor of 2 and half as many CSS pixels, which is how Chrome applies page zoom (devicePixelRatio doubles at 200%), and reading the computed font size:

Heading size on screen, in window pixels, at 100% and 200% zoom (Chrome 156, 16-pixel default)
Window width32px4vwclamp(1.5rem, 1rem + 2vw, 3rem)
40032 → 6416 → 1624 → 48 (×2.00)
80032 → 6432 → 3232 → 48 (×1.50)
1,28032 → 6451.2 → 51.241.6 → 57.6 (×1.38)
1,60032 → 6464 → 6448 → 64 (×1.33)
Heading size against window width, at 100% and 200% zoomA graph with window width from 320 to 1920 pixels across and heading size on screen from 0 to 100 pixels up. The 4vw line rises straight from 13 to 77 pixels and is the same line at 100% and at 200% zoom. The clamp at 100% is flat at 24 pixels, rises to 48 at a 1600-pixel window and stays flat. The clamp at 200% is flat at 48 pixels up to an 800-pixel window, then rises to about 70 at 1920. A dashed line shows double the 100% size, the 200% target; the clamp at 200% meets it only below a 400-pixel window. At a 1280-pixel window the clamp grows from 41.6 to 57.6 pixels, short of the 83.2 that would be double.020406080100320800128016001920window width (pixels)41.657.683.2 = double4vw, at 100% and 200%clamp at 100%clamp at 200%double the clamp at 100%
Zoom moves the clamp's line up, most where the rem bounds hold and least in the fluid middle. The 4vw line does not move at all.

In the fluid range, the preferred value at zoom z is z × (a + b·W/z) = z·a + b·W on screen, for a rem term a, a vw slope b and a window W: only the rem part is multiplied. So at 200%, any line with a vw term grows by less than double. Whether that fails 1.4.4 depends on reading the criterion, which says text must be resizable to 200% but not that 200% zoom must do it. Chrome and Firefox zoom to 500% (Safari stops at 300%), and Maxwell Barvian's "Addressing Accessibility Concerns With Using Fluid Type" (Smashing Magazine, November 2023) works out a rule from that: if the maximum size is at most 2.5 times the minimum, some zoom level up to 500% always doubles the text. The reason is short: a clamp is never below MIN in CSS pixels, so at 500% the text is at least 5 × MIN on screen, and the unzoomed text is at most MAX, so 5 × MIN reaches 2 × MAX whenever MAX ≤ 2.5 × MIN. In Safari, which stops at 300%, the same argument needs MAX ≤ 1.5 × MIN. The clamp above, with a maximum twice its minimum, reaches double at about 350% in a 1,280-pixel window. Adrian Roselli's "Responsive Type and Zoom" (2019, updated since) takes the stricter view, with clamp() examples that fail, and his advice holds either way: zoom the page and check that the text really gets twice as large.

@layer tokens { :root { --step-0: clamp(1rem, 0.93rem + 0.36vw, 1.25rem); --step-3: clamp(1.5rem, 1rem + 2vw, 3rem); } }

Test with the browser's own zoom, not a CSS stand-in: zoom: 2 on the root, measured in Chrome, doubles vw text too, so a page that fails real zoom passes it. The lab below simulates zoom the way the browser applies it.

Container units have the same trap

Container query units such as cqi, one percent of the nearest container's inline size, let a component's type follow its slot rather than the window (Chrome 105, Firefox 110, Safari 16). Under zoom they behave like the width they measure. A container that spans the window shrinks in CSS pixels as the reader zooms, just as the viewport does, and text in cqi alone stays the same size on screen; measured in Chrome at 200%, 4cqi in a full-width container matched 4vw exactly. A container whose width is capped in rem grows with zoom, and so does its cqi text. The same remedy applies: a rem term and rem bounds, as in clamp(1rem, 0.75rem + 3cqi, 2rem).

7. Leading, tracking and kerning in CSS

The three spacing words from metal type map onto CSS properties. Leading is line-height, covered above. The other two act along the line, and the course deck's slide gives the distinction: tracking is the spacing between all characters, kerning the spacing between specific pairs. The text properties page introduces them; this section is about using them well.

Tracking: letter-spacing

A text face is spaced by its designer for its intended sizes, so body text rarely wants any letter-spacing at all. Three cases do:

  • Capitals. Capitals were designed to begin words beside lowercase letters, and set together they look crowded. A small positive value, around 0.05em to 0.1em, opens them up, and is the conventional partner of text-transform: uppercase.
  • Small text. Very small sizes, such as labels and legal text, often read better with a touch of extra space, since the letter shapes need room to separate. Fonts with an optical size axis do some of this themselves; Variable fonts explains opsz.
  • Large display type often looks loose, and a slightly negative value tightens it. Go gently: touching letters are worse than loose ones.

Measure tracking in em, so it scales with the text it belongs to; a value in pixels that looks right on a 48-pixel heading is a crowd on 12-pixel text. Percentages, now also allowed by CSS Text 4 and supported in Chrome 145, Firefox 133 and Safari 17.4, mean the same thing as em here. Two cautions. CSS Text says browsers should not apply optional ligatures when letter spacing is not zero, so a tracked heading loses its fi and fl ligatures, which is usually what you want; The details covers ligatures. And never track a joined script: Arabic, Syriac, N'Ko, Mongolian and Devanagari's headline connect letters into words, and spacing them pulls the joins apart. Type for people comes back to this.

@layer base { hgroup > p:first-child, th[scope="col"] { text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.8125rem; } h1 { letter-spacing: -0.01em; } :lang(ar), :lang(hi) { letter-spacing: normal; } /* never track joined scripts */ }

Word spacing

word-spacing adds to the space between words, also best in em. A font's space is part of its design, so it is rarely needed, except to keep tracked capitals' words distinct; readers who need more can set it themselves, as WCAG's overrides below assume.

Kerning: font-kerning

Kerning pairs live in the font, in its GPOS table, and browsers apply them by default: font-kerning: auto is the initial value, and lets the browser decide, which in practice means kerning on. normal forces it on and none turns it off. Leave it on: turning it off reopens the holes in pairs like AV and To that the designer took care to close.

8. Where lines break

Browsers break lines greedily: each line takes as many words as fit. It is fast and usually fine, but it leaves headings with one word on the last line and paragraphs ending in a stranded short word. CSS now has tools for both.

Balanced headings: text-wrap: balance

text-wrap: balance chooses breaks that make the lines roughly equal in length, so a two-line heading splits near the middle instead of leaving a word behind. Balancing is expensive, so browsers limit it: Chromium balances only blocks of six lines or fewer and Firefox ten, while WebKit sets no limit; past the limit, text wraps normally. Use it for headings, captions and short pull quotes, never for body text. It works in Chrome 114, Firefox 121 and Safari 17.5, and became Baseline in May 2024.

Better paragraphs: text-wrap: pretty

text-wrap: pretty asks for better breaks at some cost in speed, details left to the browser. Chrome, since version 117, looks at the last lines of a paragraph (four, in its source) and mainly avoids a lone short word on the final line. Safari, since version 26, considers the whole paragraph, evening out the rag of the right edge and reducing hyphenation as well as short last lines. Firefox does not support it yet. Since a browser that does not understand it wraps as usual, it is a safe progressive enhancement for body text.

@layer base { h1, h2, h3, figcaption { text-wrap: balance; } p, li { text-wrap: pretty; } }

Hyphenation

hyphens: auto lets the browser hyphenate words at line ends using a dictionary for the text's language, which it finds from the lang attribute. Without lang it does nothing, and with the wrong one it breaks words in the wrong places, one more reason to set lang on every page, as CSS for people explains. Support is now broad: Firefox and Safari have had it for many years, and Chrome on every desktop platform since version 88 (before that, only on macOS and Android). The dictionaries differ by browser, so the breaks do too.

Hyphenation helps most in narrow columns and justified text. Two companions refine it. hyphenate-limit-chars: 6 3 3 hyphenates only words of at least six characters, with at least three before and after the break, avoiding fragments like "a-" at a line end; it works in Chrome 109 and Firefox 137, but not yet in released Safari, which simply ignores it. hyphenate-character changes the hyphen itself, for languages that use another mark. And for a single word, a soft hyphen (­) in the HTML marks where it may break, which works even with hyphens: manual, the default.

@layer base { article p { hyphens: auto; /* needs lang on html or the element */ hyphenate-limit-chars: 6 3 3; } h1, h2, h3 { hyphens: manual; } /* do not hyphenate headings */ }

Long strings: overflow-wrap

A long URL or file path has no break opportunity at all, and on a narrow screen it pushes past its container and makes the page scroll sideways. overflow-wrap: break-word allows a break anywhere in such a string, but only when it would otherwise overflow; overflow-wrap: anywhere does the same and also lets the browser count those breaks when sizing boxes to their content, which matters in flex and grid items. Neither adds a hyphen; anywhere is a good default for user-generated text.

9. Surviving the reader's spacing: WCAG 1.4.12

Some readers, with low vision, dyslexia or tired eyes, use an extension or user style sheet that adds space on every site they visit. WCAG 2.2 success criterion 1.4.12, Text Spacing, at level AA, makes room for them:

In content implemented using markup languages that support the following text style properties, no loss of content or functionality occurs by setting all of the following and by changing no other style property:

  • Line height (line spacing) to at least 1.5 times the font size;
  • Spacing following paragraphs to at least 2 times the font size;
  • Letter spacing (tracking) to at least 0.12 times the font size;
  • Word spacing to at least 0.16 times the font size.

An exception covers languages and scripts that do not use one of these properties: Arabic is not letter-spaced, for example. Read the criterion carefully, because it is easy to misread. It does not ask the designer to use these values; a design with a line height of 1.3 is fine. It asks that the page still work when the reader imposes them. The spacing makes text take more room: in the measure lab, the letter and word spacing alone cut Literata from 80 characters per line to 59, about a third more width for the same words, and the line and paragraph spacing add height. What fails is anything that assumed the text would fit a fixed box:

  • a fixed height or block-size on a card, button or banner, with overflow: hidden, which clips the extra lines;
  • a fixed width on a button or tab, so the label overflows or is cut;
  • text positioned absolutely over an image, which spreads past it;
  • a truncated line, with text-overflow: ellipsis or -webkit-line-clamp, that hides content the reader has no other way to reach.

The remedy is the flexible layout that serves zoom: minimum sizes instead of fixed ones (min-block-size), boxes that grow with their content, and labels that can wrap.

/* A bookmarklet adds !important to each, to beat the page's own rules. */ * { line-height: 1.5; letter-spacing: 0.12em; word-spacing: 0.16em; } p { margin-block-end: 2em; }

Steve Faulkner's text spacing bookmarklet applies exactly these rules to any page, and is the quickest test: run it, then look for clipped, overlapping or missing text. The measure lab's toggle above does the same within the lab, and shows a card with a fixed height losing its last lines while a card with a minimum height simply grows.

10. Small screens

A phone changes three things at once: the screen is narrow, it is held closer to the eyes than a monitor, and it is read in every kind of light, often on the move. Each pulls the settings in a different direction.

  • Base size. Keep body text at the reader's default, 1rem. Phones are held closer than monitors, and a 16-pixel default reads comfortably at that distance. The common failure is not phone text that is too large but desktop text shrunk to fit: 13 or 14 pixels because "it is a small screen". Choosing and pairing makes the same point about faces: large x-heights and sturdy strokes hold up best.
  • Measure. On a phone, the screen sets the measure, not ch. Measured in the lab, a 390-pixel viewport with a 1rem gutter on each side gives about 47 characters per line of 16-pixel text, in both Literata and Inter: inside the range, close to Bringhurst's 40 to 50 for narrow columns, with nothing to set. The danger is the other way: side padding in large fixed amounts, or a sidebar that refuses to stack, squeezing lines down to 25 or 30 characters.
  • Scale. Large headings on a narrow screen break into many short lines; a smaller ratio, or a fluid scale, keeps them in proportion.

Text inflation and text-size-adjust

Mobile browsers once had to show desktop pages on 320-pixel screens. Some "boosted" text, enlarging paragraphs they judged too small to read when the page was zoomed out to fit, and Safari on iPhone enlarges text when the phone turns to landscape, as Kilian Valkhof describes in "Your CSS reset needs text-size-adjust (probably)" (2022). text-size-adjust controls this inflation. It is unprefixed in Chrome, including on Android, and in Safari on iOS it still needs the prefix, -webkit-text-size-adjust. Desktop Firefox does not use it.

@layer reset { html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } }

On a page with a <meta name="viewport" content="width=device-width"> and sizes in rem, the page already fits the phone and inflation has nothing useful to do, so turning it off keeps sizes as designed. Older advice warned that none, instead of 100%, stopped readers from zooming in some browsers; Valkhof reports that bug fixed and recommends none, though 100% remains the more conservative choice. Chrome's release notes for version 127 say that in Chrome any value other than auto now turns off all automatic adjustment. What these do not affect is the reader's own choices: browser zoom, and the text size settings phones offer, still work, and a design in rem follows them.

11. Vertical rhythm, in brief

In print, text in a well-made book sits on a baseline grid: every line of body text, in every column and on both sides of the page, falls on the same set of horizontal lines, and headings and images take whole multiples of the line. The result is a calm, even texture called vertical rhythm.

CSS has no baseline grid. The usual approximation is to measure vertical space in lines of body text, which rlh now does directly, so paragraph gaps, margins around headings and spacing around figures are all multiples or simple fractions of one line:

@layer base { :root { line-height: 1.5; } p, ul, ol, blockquote { margin-block: 0 1rlh; } h2 { margin-block: 2rlh 0.5rlh; line-height: 1.2; } figure { margin-block: 1rlh; } }

It stays an approximation: a heading with a different size and line height has different baselines, and images rarely come in whole lines. Text also carries the half-leading from section 3 above its first line, so it never sits flush with the top of its box. The text-box properties trim that space to the cap height or the x-height, and as of October 2026 are in Chrome 133, Safari 18.2 and Firefox 154, which makes optical alignment of text with icons and box edges far easier. Aim for consistent spacing rather than a perfect grid: spaces drawn from a few related values read as designed.

12. Summary

Size, spacing and measure, in brief
ConceptKey points
The reader's sizeBrowsers default to 16 pixels; readers change it. Set type in rem and never size html in pixels. The 62.5% trick respects the reader but breaks components built for 1rem = 16px.
Units for typerem for size, em for spacing tied to the text, a number for line-height, ch for measure, lh/rlh for space in lines, px for hairlines. em in font-size compounds.
Line heightAscent plus descent, with the leading split above and below. A number inherits as a ratio, a length as pixels. Larger text needs less.
Measure45 to 75 characters (Bringhurst); on screen, long lines read no slower but moderate ones are preferred. 65ch holds 80 or more characters in many proportional fonts; measure yours.
Type scalesA base and a ratio give five to eight related sizes: a tool for consistency, not a rule.
Fluid type and zoomText in vw or cqi alone does not grow with zoom (F94, WCAG 1.4.4). Use clamp() with rem bounds and a rem term, a maximum at most 2.5 times the minimum, and test with real zoom.
Tracking and kerningletter-spacing in em, for capitals, small and display text; never on joined scripts. Kerning comes from the font; leave it on.
Line breakingtext-wrap: balance for short blocks (limits: 6 lines in Chromium, 10 in Firefox); pretty for prose (Chrome, Safari); hyphens: auto needs lang; overflow-wrap: anywhere for long strings.
Text spacingWCAG 1.4.12: survive line height 1.5, paragraph spacing 2, letter spacing 0.12 and word spacing 0.16 times the font size. Fixed heights fail; minimum sizes pass.
Small screensBody text at 1rem; the screen sets the measure (about 47 characters at 390 pixels); text-size-adjust stops automatic inflation, not the reader's zoom.
RhythmNo baseline grid in CSS; spacing in rlh approximates one, and text-box trims the half-leading for alignment.