⌨️ Web development

Web fonts without slowing your site: WOFF2, font-display, subsets

How to load web fonts efficiently: WOFF2, font-display, preload, subsetting with unicode-range, variable fonts and fallbacks tuned to cut layout shift.

Typography is an essential part of a site's identity, and web fonts let you use typefaces other than those installed on the device. They come at a cost, however: they are files to download, often discovered late by the browser, and until they arrive the text may stay invisible or suddenly change appearance. With a few precautions you can have well-crafted type without penalising the speed and stability of the page.

The problem: invisible text or jumping text

When a CSS rule requires a font that has not yet been downloaded, the browser has two options:

  • FOIT (Flash of Invisible Text): it hides the text until the font arrives. On a slow connection, users see a page with blank spaces where the headings should be.
  • FOUT (Flash of Unstyled Text): it shows the text immediately in a fallback font and swaps it when the web font is ready. Content is readable straight away, but the swap can change the text size and shift the layout.

In general the second behaviour is preferable: reading right away matters more than how the first moments look. It is controlled with font-display.

font-display

The property is declared in the @font-face rule:

@font-face {
  font-family: "Body";
  src: url("/fonts/body-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

The main values:

Value Behaviour
swap shows the fallback font immediately and swaps it when the web font arrives
optional very short wait; if the font is not ready, uses the fallback for the whole visit
fallback short wait, then fallback; swaps only if the font arrives within a short time
block hides the text for a longer period before using the fallback

swap is the most common choice for body text. optional is interesting when visual stability matters a lot: on the first visit users may see the system font, but the web font is cached and used on later visits, with no shift at all.

A single format: WOFF2

There is no longer any need to list five different formats in src. WOFF2 offers the best compression and is supported by all current browsers. If you receive a font in TTF or OTF format, convert it to WOFF2 before publishing it, checking that the font licence allows web use.

Hosting fonts on your own domain

Loading fonts from an external service is convenient, but it means an additional connection to another domain and, in some cases, sending the visitor's IP address to third parties, with possible implications for user privacy. Hosting the WOFF2 files alongside the rest of the site:

  • removes the additional connection;
  • gives you full control over caching and versions;
  • simplifies the Content Security Policy;
  • avoids passing visitor data to external providers.

With file names that contain a hash you can give fonts a very long cache lifetime.

Preloading critical fonts

The browser discovers a font only after downloading the CSS and finding an element that uses it: often that is late. For the main body font, a preload brings the download forward:

<link rel="preload" href="/fonts/body-regular.woff2"
      as="font" type="font/woff2" crossorigin>

The crossorigin attribute is required even for fonts on the same domain, otherwise the file is downloaded twice. Preload only one or two truly essential files: preloading every variant (italic, bold, semibold) takes bandwidth away from more important resources.

Reducing weight: subsets and unicode-range

A full font can contain thousands of glyphs for alphabets your site will never use. Subsetting creates a version containing only the characters you need, for example the Latin alphabet with accents and punctuation. The savings can be considerable.

With unicode-range you can split the font into several files and let the browser download only those containing the characters actually present on the page:

@font-face {
  font-family: "Body";
  src: url("/fonts/body-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2018-201F;
  font-display: swap;
}

Make sure the subset includes every character you need: accented letters, typographic quotes, the euro sign, long dashes. A missing character is shown in the fallback font, creating an inconsistent effect.

Variable fonts

A variable font contains a whole range of weights (and sometimes widths or slants) in a single file. If you use three or four weights of the same typeface, a variable file is often lighter than the sum of the static files and also offers intermediate weights:

@font-face {
  font-family: "Body";
  src: url("/fonts/body-variable.woff2") format("woff2");
  font-weight: 300 800;
  font-display: swap;
}
h1 { font-weight: 720; }

Tuning the fallback font

When the web font replaces the fallback, the difference in letter width and height moves the text and contributes to CLS. The size-adjust, ascent-override, descent-override and line-gap-override descriptors let you adapt a system font to the web font's metrics:

@font-face {
  font-family: "Body Fallback";
  src: local("Arial");
  size-adjust: 105%;
  ascent-override: 92%;
}
body {
  font-family: "Body", "Body Fallback", sans-serif;
}

Consider system fonts

For interfaces, admin panels and long texts, a system font stack (system-ui, -apple-system, "Segoe UI", Roboto, sans-serif) offers highly legible typefaces that are already on the device and cost nothing. You can reserve the web font for headings, where visual identity matters most.

In short

  • Use font-display: swap or optional so text is not hidden while loading.
  • Publish only WOFF2 and, where possible, host fonts on your own domain.
  • Preload only the main body font, with the crossorigin attribute.
  • Cut weight with subsets, unicode-range and variable fonts when you use several weights.
  • Tune the fallback font with size-adjust to limit layout shifts.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also