Technote

Performance optimization Advanced

Why the page jumps when your font arrives

When a late web font swaps in, the line count changes and everything below moves. Match the fallback family's metrics to the web font and the shift disappears.

You open a page, the text appears, and a moment later it changes shape — the heading folds from three lines to two and everything below jumps upward. Sometimes the button someone was about to press moves out from under them. This is a late web font arriving, and it is a loading-order problem rather than a design fault.

What is actually happening

The browser has to draw text while the font file is still downloading, so it draws with a fallback face from the system and swaps when the web font arrives. The trouble is that the two faces have different metrics.

At the same nominal size, different faces have different character widths and occupy different heights above and below the baseline. Different widths mean a different number of characters per line, which means a different line count, which moves the vertical position of everything underneath. That is the exact route from a font swap to a jumping page.

The route to layout shift — the goal is to remove the last two steps

font-display picks a trade-off, it does not fix this

The first property people reach for is font-display, but all it does is let you choose what to give up. It does not remove the shift.

Choosing a trade-off and removing the cause are different jobs

Most sites use swap. It is the safest choice in that text is never invisible, but the shift at swap time remains. Removing it means making the fallback occupy exactly the same space as the web font.

Metric overrides — tune the fallback to the web font

The technique is to stop using the system face directly and instead declare an adjusted fallback family. CSS provides the properties for it.

@font-face {
  font-family: 'Brand Fallback';
  src: local('Arial'), local('Helvetica Neue');

  /* Measure these four yourself: render the web font and the
     fallback at the same size and compare character width and
     the heights above and below the baseline */
  size-adjust:         100%;
  ascent-override:     100%;
  descent-override:     22%;
  line-gap-override:     0%;
}

body {
  font-family: 'Brand Sans', 'Brand Fallback', sans-serif;
}

size-adjust matches character width; ascent-override and descent-override match line height. Measure the values for your own pairing — render both faces at the same size, compare widths and baseline offsets, and derive the ratios. Numbers copied from someone else’s site are simply wrong for a different typeface.

When the overrides are right, the fallback and the web font break lines in the same places. The swap still happens, but only the letterforms change; positions stay put and nothing jumps.

Large character sets add one more condition

Fonts covering Korean, Japanese or Chinese carry thousands of glyphs and the files are correspondingly large, so they are usually shipped as subsets split by Unicode range, with the browser fetching only the pieces containing glyphs the page actually uses. That is a good structure, but several pieces means several arrival times and potentially several shifts.

Preloading only the subsets used on the first screen works well here. Keep the list short, though: preloads compete for bandwidth with other first-screen resources such as the hero image. And because the required subsets follow the words on the page, revisit the list whenever the hero copy changes.

Typography as a whole is covered in the typography series, and other articles on first-screen loading live in the performance archive. Fonts, images and caching are handled together in our optimization program.

More on this topic

All technotes

Performance optimization Practical

Who pays for the milliseconds a tag adds?

A tracking script does not stop at downloading. While it is parsed and executed the main thread is busy, and a busy page ignores fingers. That cost is…

Marketers 5 min read

Performance optimization Practical

The object cache: what Redis actually removes

Repeated identical lookups disappear. One slow query does not. Miss that distinction and you will wire up Redis and then ask why nothing got faster.

Developers 7 min read

Performance optimization Practical

This is how a landing page gets heavy

Nobody decides to make a landing page heavy. It accumulates: one addition per campaign, and nothing ever removed when the campaign ends.

Marketers 6 min read

₩270,000 · Join the program