Web Typography

Typography is most of what an interface is. Strip the color and the imagery from almost any application and text remains — which means the decisions about size, spacing, measure, and weight determine both how it looks and how comfortably it can be read. It's also the area where small systematic choices have the largest aggregate effect: a well-chosen scale and a correct line length improve every screen at once.

On the web there's a second dimension that print doesn't have: fonts are a network resource. A custom font that arrives late causes either invisible text or a visible reflow, and both are measurable regressions in Core Web Vitals. Good web typography is therefore a design problem and a performance problem simultaneously, and the two are solved together.

TL;DR

Quick Example

A complete type foundation — scale, fluid sizing, and shift-free font loading:

The size-adjust fallback is the part most setups omit and the part that eliminates the visible jump when the webfont loads — a real CLS improvement, not just an aesthetic one.

Core Concepts

The modular scale

A scale gives you a small set of sizes that relate to each other. Without it you get 14px, 15px, 16px, and 17px in the same interface — differences too small to read as hierarchy and large enough to look accidental. Interfaces generally want a tighter ratio (1.125–1.25) than editorial pages (1.333+), because UI needs many closely spaced steps.

Line length and line height

Line height moves inversely with size and with measure:

Set line-height unitless (1.5, not 24px) so it scales with inherited font size. A fixed pixel line height on a container whose children vary in size produces cramped or spaced-out text unpredictably.

Font loading

swap is the right default: users see content immediately, and the swap causes a reflow you can eliminate with size-adjust. optional is excellent for non-critical fonts — the browser uses the webfont only if it's already cached or arrives essentially instantly, so repeat visitors get the design and first-time visitors get speed.

The full loading strategy:

Subsetting a font to Latin characters commonly cuts it by 70% or more. Tools: glyphhanger, subfont, or pyftsubset from fonttools.

Metric-compatible fallbacks

The layout shift when a webfont swaps in happens because the fallback has different metrics — different character widths, ascent, and descent. The @font-face descriptors fix it:

Generate the numbers with the Fontaine library or a tool like capsize rather than by trial and error. Next.js's next/font does this automatically, which is a substantial part of its value.

Variable fonts

One file containing a continuous range along one or more axes:

The break-even is around three weights — below that, static files are smaller. Common axes are weight (wght), width (wdth), optical size (opsz), and slant (slnt); opsz in particular is a genuine quality improvement, adjusting letterform details for the size being rendered.

System font stacks

Zero bytes, zero loading concerns, zero layout shift, and it looks native on every platform. For application UI — dashboards, admin tools, internal software — this is frequently the correct choice, and the reason so many products that look carefully typeset are using it. Reserve custom fonts for where brand expression genuinely matters.

Best Practices

Always size in rem, never px

Users who increase their default font size are usually doing so because they need to. A pixel-sized interface silently overrides that, and it's an accessibility failure that never shows up in testing because developers rarely change the setting.

Constrain the measure

max-width: 65ch on paragraph containers. This is the highest-value single line of typography CSS — full-width text on a wide monitor is genuinely hard to read, and almost nobody notices until it's fixed.

Use text-wrap: balance and pretty

balance on headings distributes words evenly across lines instead of leaving one word alone. pretty on paragraphs prevents orphans. Both are single declarations with a visible quality improvement, and they degrade harmlessly where unsupported.

Tighten letter spacing on large text, loosen on small

Type designed for body sizes looks loose when scaled up. letter-spacing: -0.02em on headings and a slight positive value on small caps or uppercase labels is a small adjustment that separates careful typography from default typography.

Self-host fonts

Third-party font CDNs cost a DNS lookup and a connection, and browser cache partitioning means a font cached from another site no longer helps yours. Self-hosting with preload is faster and removes a third-party dependency and a privacy consideration.

Limit weights and styles

Each weight is a download. Most interfaces need two or three (regular, medium, bold). Faux-bolding by the browser looks bad, but so does loading six weights to use two.

Make the type scale tokens

Sizes referenced from a token set are consistent by construction and adjustable in one place. Arbitrary font-size: 17px in a component is the same problem as an arbitrary hex color.

Test with a real content sample

Lorem ipsum has uniform word lengths and hides the problems real text creates: long unbroken URLs, all-caps acronyms, names with diacritics, and headings that are too long. Set your type against actual content before shipping.

Common Mistakes

Full-width paragraphs

Fixed pixel line height

Loading fonts without a display strategy

Preloading every font

Ignoring the layout shift

Sizing text in px

Justified text on the web

FAQ

How many fonts should a site use?

One or two. A single well-chosen typeface with a range of weights handles nearly everything; a second is justified when headings need a distinct voice from body text. Three or more is almost always accidental accumulation rather than a decision, and each one costs bytes and consistency.

Serif or sans-serif for body text?

Both are readable on modern high-density displays — the old advice favoring sans-serif dates from low-resolution screens where serifs blurred. Choose on voice and context: serif reads as editorial, traditional, and long-form; sans reads as neutral, modern, and interface-like. What matters far more than the category is size, measure, and line height.

Are Google Fonts still a good option?

The library is excellent and worth using — but download the files and self-host them rather than linking to the CDN. Browser cache partitioning removed the shared-cache benefit that was the original argument for the CDN, and self-hosting is faster, removes a third-party request, and avoids the privacy and regulatory questions that using the hosted version has raised in some jurisdictions.

How do I handle non-Latin scripts?

Verify the typeface actually supports the scripts you need — many popular Latin fonts have no Cyrillic, Greek, CJK, or Arabic coverage. Use unicode-range to split subsets so a page in English doesn't download the CJK glyphs. Be aware that scripts have different metric requirements: CJK typically needs more line height, and Arabic and Devanagari need more vertical space and careful font selection. See Internationalization.

Does typography affect performance scores?

Directly. Fonts block or delay text rendering, affecting LCP when the largest element is text; font swapping causes reflow, affecting CLS; and font files compete for bandwidth with everything else. A metric-matched fallback, font-display: swap, aggressive subsetting, and preloading only the critical font address all three. See Core Web Vitals.

What is optical sizing and does it matter?

The opsz axis on a variable font adjusts letterform details — stroke contrast, spacing, x-height — for the size being rendered, the way metal type had distinct cuts for different sizes. Text set at 10px and at 72px genuinely wants different letterforms, and font-optical-sizing: auto applies it automatically when the font supports it. It's a subtle refinement that improves both small-text legibility and large-text elegance for free.

Related Topics

References