Web Font Loading Best Practices: Fast, Visible, Stable Text
- Difficulty
- Moderate
- You need
- Chrome DevTools, access to your CSS
- Optional
- A font subsetting tool
On this page
Web font loading is about getting the right font files to the browser early, in as few and as small files as possible, while keeping text visible and the layout steady until they arrive. Use fewer fonts, serve WOFF2, preload only what's critical, choose a font-display value on purpose and tune the fallback font to match. This guide is part of our website design best practices guide.
You'll need Chrome DevTools and access to your CSS and the document head. A subsetting tool helps if you self-host fonts. Fonts affect two Core Web Vitals: Largest Contentful Paint when the main content is text, and Cumulative Layout Shift when the swap moves text.
How browsers load web fonts
A font file isn't downloaded just because an @font-face rule exists. The browser fetches it only once styling on the page actually uses that font, which means it often discovers fonts late: after the HTML, after the CSS, and after working out which elements need them.
While the font is missing, the browser has two choices. It can hide the text for a short time and wait, which is called the block period. Or it can show the text in a fallback font and swap in the web font when it arrives, which is the swap period. The font-display descriptor in @font-face decides how long each period lasts.
Hidden text delays the moment people can start reading, and may delay LCP. A late swap can make lines rewrap and push content down. Good font loading keeps both short.
font-display values compared
Each value trades a little speed, stability or brand consistency against the others. The Chrome team's font guide lists how long each value blocks and swaps:
| Value | Block period | Swap period | Good for |
|---|---|---|---|
| auto | Browser decides | Browser decides | Rarely the best explicit choice |
| block | 2 to 3 seconds | Indefinite | Icon fonts only, if you must |
| swap | None | Indefinite | Body text and key brand headings |
| fallback | About 100 ms | About 3 seconds | Body text where late swaps hurt |
| optional | About 100 ms | None | Decorative or nice-to-have fonts |
With optional, a visitor on a slow first visit may see only the fallback font. Once the font is cached, later pages use it straight away.
Steps
- List every font file the page downloads.
In Chrome DevTools, open the Network panel, filter by Font and reload the page. Note each file's name, size and when it starts loading. Then compare the list with the design. Pages often load weights or styles no one uses, or the same family from two places, such as a theme and a plugin.
- Cut the number of families, weights and styles.
Each weight and style is a separate download unless you use a variable font. Most sites need one family for text and at most one for headings. Consider a system font stack such as `system-ui` for body text, which needs no download at all. If you use three or more weights of one family, a single variable font file may be smaller.
- Serve WOFF2, subset to the characters you use.
WOFF2 is the most compressed font format and has the widest support among modern formats, so you rarely need anything else. Subsetting removes characters you don't use, such as other alphabets. Third-party services often do this for you. Use unicode-range so the browser only downloads a subset when the page needs those characters.
- Decide where fonts are hosted, and connect early.
Self-hosting avoids connecting to another server, though the Chrome team notes that real-world results aren't clear-cut. If you load fonts from a third-party service, add preconnect hints in the head, one for the stylesheet host and one with crossorigin for the font file host. Font files are always fetched in CORS mode.
- Put @font-face in the head, and preload one or two fonts.
Inline the @font-face rules in the head so the browser finds them without waiting for an external stylesheet. Then preload only the one or two files used above the fold, such as the heading and body regular weights. Preloading more makes fonts compete with the hero image for bandwidth:
html<link rel="preload" href="/fonts/brand-sans-400.woff2" as="font" type="font/woff2" crossorigin> - Choose a font-display value for each font.
Use the table above. Swap suits body text and brand headings that should always appear in the web font. Optional suits fonts whose late arrival isn't worth a shift. Set it in each @font-face rule:
css@font-face { font-family: "Brand Sans"; src: url("/fonts/brand-sans-400.woff2") format("woff2"); font-weight: 400; font-display: swap; } - Tune the fallback font to match.
Declare a fallback that's already installed, then adjust its metrics so it takes up the same space as the web font. The size-adjust, ascent-override and descent-override descriptors do this. When the swap happens, lines then wrap in the same places and nothing below moves:
css@font-face { font-family: "Brand Sans Fallback"; src: local("Arial"); size-adjust: 104%; ascent-override: 92%; descent-override: 24%; } body { font-family: "Brand Sans", "Brand Sans Fallback", sans-serif; } - Test on a slow connection.
In DevTools, set network throttling to a slow mobile profile, clear the cache and reload. Watch whether text appears straight away, how long the fallback shows and whether anything moves at the swap. Record a Performance trace to see font requests next to the LCP and any layout shifts. Repeat with the cache warm to check later page views.
For a wider check of the whole site, see our website speed test guide.
The percentages in the fallback example are placeholders: work out real values for your font pair. If you'd rather have typography and loading planned for you, our in-house web team designs and develops sites with performance built in.
Readable fonts for every visitor
Fonts decide whether people can read the page, not just how fast it renders. A few loading and styling choices make a big difference for people with low vision, dyslexia or slow connections.
Keep text visible while fonts load. A long block period means a blank page for anyone on a slow connection. Avoid `font-display: block` for text. Swap, fallback or optional all show readable text almost at once.
Let people resize and respace text. WCAG's Resize Text criterion asks that text can be enlarged to 200 percent without losing content or function. Its Text Spacing criterion asks that nothing breaks when people raise line height to 1.5 times the font size, paragraph spacing to 2 times, letter spacing to 0.12 times and word spacing to 0.16 times. Set font sizes in rem or em, avoid fixed line heights in pixels, and don't put text in boxes with fixed heights.
Don't rely on icon fonts. Icon fonts are a common reason to use `font-display: block`, and if the font fails, screen readers may announce a meaningless character. We prefer inline SVG icons with an accessible name, or a hidden text label, which also removes a font download.
Check weight and contrast together. Thin weights and small sizes can fail WCAG's Contrast (Minimum) criterion of 4.5:1 for normal text, even when the colour looks fine in the design file. Check contrast with the weight and size used on the live page.
Use real text, not images of text. A web font makes brand typography possible in HTML, so headings and buttons rarely need to be images. Real text can be read aloud, translated, resized and indexed.
Troubleshooting
Text is invisible for a moment on slow connections
Cause: the font uses `font-display: block`, or `auto`, which often behaves like block, so text stays hidden for up to a few seconds while the file downloads. Fonts discovered late make this worse, because the hidden period starts only once the browser has worked out it needs the font. It shows most on first visits over mobile data.
Fix: set `font-display` to swap, fallback or optional in every @font-face rule, including rules from third-party services, which often accept a display parameter. Inline the @font-face rules in the head so they're found early, and preload the main text font. Then test again with throttling and an empty cache. Text should appear in the fallback font immediately, then switch without hiding.
The preloaded font downloads twice
Cause: the preload and the @font-face rule don't match, so the browser treats them as different requests. The usual reasons are a missing crossorigin attribute on the preload link, or a URL that differs slightly, such as a different version string or path. DevTools may also warn that a preloaded resource wasn't used within a few seconds of the load.
Fix: add crossorigin to the preload link, even for fonts on your own domain, because fonts are always fetched in CORS mode. Copy the exact URL from the @font-face src into the preload href. Preload only one format, WOFF2. Then reload with the Network panel open and confirm a single request for the file. Remove preloads for fonts that aren't used above the fold.
Bold or italic text looks smeared or fake
Cause: the page asks for a weight or style that hasn't been loaded, so the browser fakes it. It thickens the regular weight for bold, or slants it for italic. This often happens after cutting font files in step 2, when CSS still sets font-weight 700 or uses em and strong elements that expect real bold and italic files.
Fix: decide which weights and styles the design really needs, then either load those files or change the CSS to use only what's loaded. Search your stylesheets and content for font-weight values and italic styles. If you've chosen a variable font, check that its weight and slant ranges cover what the CSS asks for. Synthetic styles can be switched off with font-synthesis, but fix the mismatch first.
Common questions
Is font-display: swap always the right choice?
Not always. Swap shows text in a fallback font straight away, which keeps content readable, but it can cause a layout shift when the web font arrives late. It suits body text and brand headings you want to see every time. Optional avoids late swaps by using the web font only if it's ready almost at once, which suits fonts that are nice to have.
Are Google Fonts or self-hosted fonts faster?
It's less clear-cut than it sounds. Self-hosting avoids a connection to another server, but the Chrome team's own guide notes that real-world data hasn't shown a simple winner. What matters more is the number of files, WOFF2 compression, subsetting and how early the browser discovers the font. If you use a third-party service, preconnect to its font server.
Are variable fonts better for performance?
They can be. One variable font file can replace several separate weight and style files, so a design that uses three or more weights of a family often loads less with a single variable file. If you only use one or two weights, separate static files may be smaller. Compare the total download for the weights you actually use.