Reducing Layout Shift With Custom Fallback Fonts
May 12, 2023 • 4 min read
Table of Contents
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.
Font Loading Can Cause Layout Shifts
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.
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:
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:
To define custom fallbacks, you add the @font-face descriptors size-adjust, ascent-override, descent-override and/or line-gap-override like so:
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.
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:
However, the preloading strategy can backfire if you preload too many unused, large or redundant font files like so:
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.
Without preloaded fonts (both with and without custom fallback fonts), the page is quicker to start rendering.
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.