Typography for Interfaces
Type is ~90% of most interfaces' visual matter. Get it right and the rest gets easier.
Legibility fundamentals (research-backed)
line-height: 1.1 — cramped
Reading needs air between lines or ascenders and descenders collide.
line-height: 1.5 — body sweet spotReading needs air between lines or ascenders and descenders collide.
line-height: 2.0 — lines detachReading needs air between lines or ascenders and descenders collide.
word shape — lowercase
shipping
typefaces
word shape — caps: uniform rectangles
SHIPPING
TYPEFACES
- Size: Body text minimums by convention of the platform owners: 17 pt (iOS default body, Apple HIG), 16 sp (Material body-large), ~16 px on the web (browser default; going smaller measurably slows reading for many users). Legge & Bigelow (2011) show reading speed is stable across a broad middle range of sizes and collapses below a critical print size — so err larger, especially for older users.
- Line length (measure): ~45–75 characters per line for comfortable reading of body text (classic typographic guidance, Bringhurst). Very long lines make return sweeps error-prone; very short lines fragment reading.
- Line height: ~1.4–1.6× font size for body text; tighter (1.1–1.3) for large headings. (Convention; WCAG 1.4.12 requires text to survive user overrides up to 1.5 line-height.)
- Contrast: WCAG AA = 4.5:1 for normal text, 3:1 for large text (≥18 pt / 14 pt bold). Pure black on pure white is allowed and fine; claims that it causes eye strain are anecdotal, not established research.
- Casing: ALL CAPS slows continuous reading (uniform word shapes, and people have little practice reading caps); fine for short labels.
- Serif vs. sans on screens: modern research finds no consistent legibility winner at adequate sizes/resolutions; choose by tone and rendering quality, not folklore.
System of styles (type scale)
display · 39px / 700
Quiet interfaces
headline · 31px / 700
Quiet interfaces
title · 25px / 700
Quiet interfaces
body · 16px / 400 · line-height 1.5
Quiet interfaces let a handful of type roles do all the talking — five to seven sizes, two or three weights.
label · 13px / 700 / caps
Quiet interfaces
- Define roles, not ad-hoc sizes: display, headline, title, body, label / caption (this mirrors Material 3's and Apple's role-based scales).
- Build the scale on a ratio (e.g., 1.2 "minor third" or 1.25) or adopt a platform scale; limit to ~5–7 sizes and 2–3 weights actually used.
- Hierarchy via weight and size, sparingly via color; never via underline (reserved for links on the web).
- Pair at most 2 typefaces (one display/brand voice + one workhorse); a monospace third only if you display code/data.
Font pairing craft (conventions of the trade)
decisive pair — display face + workhorse (this site's own pairing)
Contrast of role,
harmony of proportion — the body face stays plain and matches apparent size.
near-miss pair — two similar grotesques
Almost the same,
which reads as an accident rather than a decision. Make the difference decisive or use one family.
- Pair by contrast of role, harmony of proportion: the two faces should differ obviously (serif display + sans body, or grotesque + humanist) but share similar x-height and apparent size — set the same sentence in both at body size; if one looks 10% bigger, compensate or repair.
- Near-miss pairs (two similar sans faces) read as a mistake, not a choice — make the difference decisive or use one family.
- Cheapest safe route: a superfamily designed to match (e.g., Source Serif/Sans, IBM Plex Serif/Sans/Mono).
- Judge pairs in a realistic mockup at real sizes, not in a specimen sheet; display-only faces often collapse below ~20 px.
Variable fonts
one file, continuous weight axis (wght)
Aa 300Aa 450
Aa 550Aa 700
Aa 800
optical size axis (opsz) — same rendered size, different drawing
Agile 12
Agile 96
opsz 12 is drawn sturdier (for captions); opsz 96 finer and tighter (for display) — automatic with font-optical-sizing: auto.
One file exposing continuous design axes (weight wght, width wdth,
optical size opsz, slant, custom axes) — OpenType 1.8 spec, 2016; well
supported in all modern browsers and OSes.
- Performance: one variable file usually beats 3–4 static weights; subset it and serve WOFF2.
- Fine hierarchy: intermediate weights (e.g., 450, 550) tune emphasis and fix light-text-on-dark looking heavier (drop ~50 weight units in dark mode — a common convention, not a standard).
- Optical sizing:
opszmakes small text sturdier and display text finer automatically (font-optical-sizing: auto). SF Pro, Segoe UI Variable, and Roboto Flex all do this; it's why platform type looks right at every size. - CSS: prefer high-level properties (
font-weight: 450) over rawfont-variation-settings, which disables property inheritance niceties.
Numerals: tabular figures & friends
proportional — misaligned
$1,111.11
$8,904.20
$941.55
$10,957.86
$8,904.20
$941.55
$10,957.86
tabular-nums — aligned
$1,111.11
$8,904.20
$941.55
$10,957.86
$8,904.20
$941.55
$10,957.86
slashed zero for codes
ID: X0O1-0O
0 vs O must never be a guess in IDs and codes.
tabular-nums fixes it. Try the interactive version on Data Tables & Dashboards.- In tables, timers, prices, dashboards, and anything that updates in place,
use tabular lining figures (
font-variant-numeric: tabular-nums) so digits share one width — columns align and counters don't jitter. - Proportional figures are for running text; oldstyle figures (lowercase- like) suit editorial prose, not UI data.
- If the font lacks a
tnumfeature, pick a different font for data surfaces; letter-spacing hacks don't fix digit widths. - Related OpenType niceties:
slashed-zerofor codes/IDs,diagonal- fractionsfor recipes/measurements.
Fluid type (web)
clamp(1.75rem, 1.2rem + 2vw, 3rem) — flat at the anchors, fluid between. The rem term keeps zoom and user font-size working (WCAG 1.4.4); headings get fluidity, body mostly doesn't need it.clamp(min, preferred, max)with arem + vwpreferred term scales type smoothly between breakpoints, e.g.font-size: clamp(1.75rem, 1.2rem + 2vw, 3rem).- Always include a rem component and sane min/max: pure-
vwsizing ignores browser zoom and user font-size, risking WCAG 1.4.4 (resize to 200%) failures. Verify text still enlarges at 200% zoom. - Scale display/headline sizes fluidly; keep body ~16–18 px across viewports — body text needs little fluidity, headings need a lot.
- Utopia (utopia.fyi, Clearleft) is the common calculator for coordinated fluid type + space scales.
Platform notes
- iOS/macOS: San Francisco (SF Pro / SF Compact); support Dynamic Type (user-scalable text) — Apple treats this as an accessibility requirement.
- Android: Roboto (Material default) / Google Sans in Google apps; sizes in sp so they scale with user settings.
- Windows: Segoe UI Variable (Fluent 2 type ramp).
- Web: use rem units so text respects browser settings; load ≤2 font
families, subset weights, use
font-display: swap.
Sources
- Bringhurst, R. The Elements of Typographic Style (Hartley & Marks) — measure, rhythm, scale.
- Legge, G. E. & Bigelow, C. A. (2011). "Does print size matter for reading?" Journal of Vision, 11(5).
- Microsoft OpenType specification v1.8 (2016) — font variations (variable fonts).
- Apple Human Interface Guidelines — Typography (developer.apple.com/design).
- Material Design 3 — Typography / type scale (m3.material.io).
- Microsoft Fluent 2 — Typography (fluent2.microsoft.design).
- W3C WCAG 2.2 — SC 1.4.3 (contrast), 1.4.4 (resize), 1.4.12 (text spacing).
- MDN —
font-variant-numeric,font-optical-sizing,clamp()(developer.mozilla.org).