Performance

Reducing layout shift with custom fallback fonts

Brian Louis Ramirez
May 12, 2023
4 min read
Contact our team
Let's check your website speed.
Contact sales
Share blog post
Everyone benefits from speed.
https://speedkit.com/blog/reducing-layout-shift-with-custom-fallback-fonts

Introduction

You go to a website and start reading the text as soon as it appears. But then the web font loads and the rest of the content shifts. How annoying! In this post, we’ll go over how to adjust system fonts to reduce layout shift. You can also watch my talk from when we hosted the Hamburg Web Performance Group Meetup on March 23, 2023.

Font loading can cause layout shifts

Fonts do not block rendering like CSS and synchronous JavaScript. But if you load web fonts (“brand” fonts) on your site, they can make the page experience less agreeable for users by causing a Flash of Unstyled Text (FOUT) and the layout of the page to shift.

A web page loading with a web font that causes a Flash of Unstyled Text and layout shift
A web page loading with a web font that causes a Flash of Unstyled Text and layout shift


FOUT can occur when:

  • the font-display descriptor in @font-face) allows for a web font to be swapped in as soon as it loads
  • the web font is visibly different from its fallback font
  • it takes a noticibly long time to download the web font
  • the web font has to be downloaded and is not loaded from a cache (e.g. on first visits)

I’ve seen a few different font loading strategies being used on websites to eliminate FOUT — such as font preloading or even embedding base64-encoded fonts in stylesheets — but they have drawbacks.

Identifying the cause of layout shifts can be tricky

How do you know when and where your visitors are experiencing layout shift? The Web Vitals library is the go-to tool for collecting Web Vitals performance metrics and attribution data that you can send to your analytics tool of choice.

CLS attribution logged to console in Chrome DevTools
CLS attribution logged to console in Chrome DevTools


Keep in mind that CLS attribution data can point out which elements shifted in the viewport — not which elements caused other elements to shift. To see whether fonts are causing FOUT and layout shift, you can hard-reload a page to see if text breaks onto new lines.

How to define custom fallback fonts

The following font loading strategy helps you to reduce FOUT and layout shift by defining system fonts to use as fallbacks and tweaking them via CSS so they roughly match the appearance of later-loading web fonts. You can use the following @font-face font descriptors to adjust how a font face is displayed:

  • size-adjust
  • ascent-override
  • descent-override
  • gap-override


At the time of writing, support for the above descriptors is 73% of users.

Step 1: Find system fonts that are close enough to the brand fonts

First, you need to find widely-supported system fonts (i.e. “web-safe” fonts) that share many typographic characteristics of the web font they briefly stand in for. For example, Arial is available most operating systems and is a good stand-in for sans-serif fonts. As not all operating systems have the same system fonts installed, you should consider adding font fallbacks for other systems.

Step 2: Adjust @font-face descriptors

Once you have system fonts that are approximate stand-ins for the web fonts that replace them, you then tweak the system fonts to better match the visual space that the web fonts take up. Alternatively, you could tweak the web fonts to match a single fallback font, but since different operating systems have different system fonts, this approach wouldn’t have the widest browser support.

You can add web fonts to a page in your CSS like this:

/* Without custom fallback */

@font-face {
    font-family: 'DancingScript';
    src: url(DancingScript.woff2) format('woff2');
    font-display: swap;
}


h1 {
    font-family: 'DancingScript';
}


To define custom fallbacks, you add the @font-face descriptors size-adjust, ascent-override, descent-override and/or line-gap-override like so:

/* With custom fallback */

@font-face {
    font-family: 'DancingScript';
    src: url(DancingScript.woff2) format('woff2');
    font-display: swap;
}


@font-face {
    font-family: 'Adjusted Times New Roman Fallback';
    src: local(Times New Roman);
    size-adjust: 92%;
    ascent-override: 97%;
    descent-override: 29%;
    line-gap-override: normal;
}


h1 {
    font-family: 'DancingScript', 'Adjusted Times New Roman Fallback';
}


Now when you reload a page that has the above CSS, the text appearance shouldn’t change too drastically once the web font is swapped in.

Font loading with and without a custom fallback font
Font loading with and without a custom fallback font


You can also use tools such as my Fallback Font Generator to help you adjust the descriptors manually, Malte Ubl’s Automatic Font Adjusting tool to automatically find font fallbacks for Google Fonts, or framework tools on @next/font or @nuxtjs/fontaine to automatically include customized fallback fonts.

What about preloading fonts?

You can drastically reduce FOUT by preloading fonts like so:

<head>
    <link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2">
</head>


However, the preloading strategy can backfire if you preload too many unused, large or redundant font files like so:

<head>
    <link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2">
    <link rel="preload" href="/fonts/Inter-Regular.woff" as="font" type="font/woff">
    <link rel="preload" href="/fonts/Inter-Bold.woff2" as="font" type="font/woff2">
    <link rel="preload" href="/fonts/Inter-Semi-Bold.woff2" as="font" type="font/woff2">
    <link rel="preload" href="/fonts/Inter-Thin.woff2" as="font" type="font/woff2">
    <link rel="preload" href="/fonts/DancingScript-Regular.ttf" as="font" type="font/ttf">
    <link rel="preload" href="/fonts/DancingScript-Bold.ttf" as="font" type="font/ttf">
</head>


To demonstrate this, I tested the following font loading strategies using WebPageTest:


On first page views, preloading web fonts led to network congestion: the render-blocking stylesheet was consequently loaded a bit later.

WebPageTest Connection View of page with preloaded fonts
WebPageTest Connection View of page with preloaded fonts
WebPageTest Connection View of page without preloaded fonts
WebPageTest Connection View of page without preloaded fonts


Without preloaded fonts (both with and without custom fallback fonts), the page is quicker to start rendering.

WebPageTest filmstrip with the results of testing different font loading strategies
WebPageTest filmstrip with the results of testing different font loading strategies

Summary

Check how fallbacks match up with brand/web fonts on your sites. If they match up really well, consider just relying on system fonts because a) system fonts are not all ugly — some are even “generally tolerable” — and b) it’s the most performant font loading strategy.

If you need brand/web fonts and they don’t match up well with your current fallback fonts, definitely customize those fallbacks by tweaking @font-face descriptors to reduce layout shift. And for the love of goodness: load WOFF2 or WOFF files, avoid loading large TTF or OTF files, sub-set your web fonts, and self-host your fonts.

If you’re considering preloading your brand/web fonts to avoid layout shift altogether, definitely have a look at your analytics and RUM data. You don’t want font preloads to clog up the network and delay page loading for your visitors on slower devices and connections.

More information

GET STARTED

Book a free website speed check

We analyze your website speed, identify web vitals issues, and compare your competitors.

Book free speed check
iPhone 15 Device CheckLaser Scanner