
Web Font Loading in WordPress: font-display, Preloading, and Layout Shift
Fonts sit right at the intersection of LCP and CLS. Here's how font-display, preloading and metric overrides actually behave, and what WordPress gives you.

Fonts are unusual among performance problems because they hurt two Core Web Vitals at once, from opposite directions. Load a font badly and your text either sits invisible while it downloads, delaying Largest Contentful Paint, or it renders in a fallback and then jumps when the real font arrives, causing Cumulative Layout Shift.
Every font loading strategy is a trade between those two failure modes. There's no configuration that avoids both entirely, and the CSS that governs the trade is small enough to understand fully in one sitting.
WordPress also added a Font Library to core in 6.5, which changed the default answer to "where do my fonts come from" for a lot of sites, and quietly solved a privacy problem along the way.
TL;DR
font-displaycontrols the trade.swaphas a zero-length block period (text visible immediately in a fallback, then swaps whenever the font arrives).optionalhas an extremely short block period and no swap period, so the font is either ready almost immediately or skipped for that page view.- Preloading requires the
crossoriginattribute even for same-origin fonts. web.dev is explicit: "Without this attribute, the preloaded font is ignored by the browser."- Don't preload everything. web.dev warns preload "can harm performance by making unnecessary requests for resources that are not used."
- Layout shift from the fallback-to-webfont swap is fixable with
size-adjust,ascent-override,descent-override, andline-gap-overrideon the fallback's@font-faceblock.- Text can be the LCP element, and web.dev notes that setting
font-displayto anything other thanautoorblockmeans "text will always be visible during load."- WordPress 6.5 added the Font Library, which stores fonts locally on your server rather than fetching them from a third-party CDN at runtime, which is both a performance and a privacy improvement.
The Three Periods
Every web font download passes through three phases, and font-display decides how long each lasts.
The block period. Text using the font is rendered invisibly. The browser draws it with a fallback face whose glyphs contain no ink, so the space is reserved but nothing is visible. This is what people call FOIT (flash of invisible text), though that acronym is community shorthand rather than spec terminology.
The swap period. Text renders in a fallback font and will swap to the web font if it arrives during this window. This is FOUT (flash of unstyled text).
The failure period. If the font hasn't loaded, the fallback is kept for this page view.
The five font-display values are combinations of those:
| Value | Block period | Swap period | Behaviour |
|---|---|---|---|
auto |
Browser-defined | Browser-defined | Whatever the browser decides; typically similar to block |
block |
Short (Chrome guidance suggests 3s) | Infinite | Invisible text during block, then fallback, swaps whenever the font arrives |
swap |
Zero | Infinite | Fallback visible immediately, swaps whenever the font arrives, however late |
fallback |
Extremely short (~100ms) | Short (~3s) | Near-immediate fallback; swaps only if the font arrives within roughly 3 seconds |
optional |
Extremely short (~100ms) | None | Browser may not download the font at all on a slow connection; if it doesn't arrive almost immediately, the fallback is kept for that render |
The specific durations are browser implementation targets rather than hard values fixed in the CSS spec. Firefox, for instance, exposes them as adjustable preferences. Treat the numbers as approximate.
Which to Choose
swap is the safe default for body text. Text is visible immediately, which protects LCP, at the cost of a swap that can cause layout shift. Pair it with the metric overrides below and you get most of the benefit without most of the cost.
optional is the best choice for CLS specifically, because there's no swap period, so there's no swap to shift the layout. The trade is that visitors on slow connections may never see your font on that page view. For a brand-critical display face, that's often unacceptable. For body copy where the fallback is close enough, it's frequently the right call.
block is defensible for icon fonts, where a fallback renders meaningless characters and invisible text is genuinely better than wrong text. It's a poor choice for body copy.
fallback is a middle ground that rarely gets chosen deliberately but works reasonably.
Preloading, and Getting the crossorigin Right
Preloading tells the browser to start fetching a font early rather than waiting until it discovers the font is needed while parsing CSS.
<link rel="preload" href="/fonts/brand-regular.woff2" as="font" type="font/woff2" crossorigin>The crossorigin attribute is mandatory, even for fonts on your own domain. web.dev states it directly: "Without this attribute, the preloaded font is ignored by the browser."
The reason is that fonts are always fetched in anonymous-mode CORS regardless of origin. If the preload request's CORS mode doesn't match the eventual font request's mode, the browser discards the preloaded resource and fetches it again. You end up downloading the font twice and having made the page slower, which is a particularly annoying way to fail.
Don't preload everything. web.dev's caution: preload "can harm performance by making unnecessary requests for resources that are not used." Preloading six font files, four of which appear below the fold or not at all on this page, competes for bandwidth with the resources that actually matter for your initial render.
The rule: preload only the fonts needed for above-the-fold content. Usually that's one or two files, your body regular and maybe a heading weight.
Fixing the Layout Shift
When the browser swaps from a fallback to your web font, the text reflows if the two fonts have different metrics. Different character widths, different line heights, and the paragraph gets taller or shorter and everything below it moves. That's CLS.
CSS provides descriptors to fix this by making the fallback match the web font's metrics before the swap happens. All go on a @font-face block for the fallback font:
size-adjust— a global scale applied to all glyphs, so the fallback's overall proportions match the incoming font. web.dev describes the goal as timing this so "there is minimal visual change, a seamless swap."ascent-override— overrides the ascent metric (the height above the baseline used for line-box layout). MDN documents CLS mitigation as its explicit use case.descent-override— the same for the descent metric below the baseline.line-gap-override— the same for line gap.
In practice:
@font-face {
font-family: 'Brand Fallback';
src: local('Arial');
size-adjust: 97%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: 'Brand', 'Brand Fallback', sans-serif;
}The percentages are specific to the pairing of your web font and your chosen fallback, and there are tools that calculate them. There's no universal set of values.
One caveat: browser support for the override descriptors has historically lagged behind the more widely-used font-display. MDN has flagged ascent-override as not yet Baseline in the past. Check current support before relying on them, and treat any specific support claim, including this one, as worth re-verifying.
Fonts and LCP
Text can be the Largest Contentful Paint element. On a text-heavy page with no hero image, it usually is.
web.dev's LCP guidance addresses this directly: when "the LCP element requires a web font to render," you've introduced a network dependency into your largest paint. Text using only system fonts has zero resource load delay. Text needing a downloaded font waits for that download.
The documented mitigation is straightforward: "If you set a font-display value of anything other than auto or block, then text will always be visible during load." So swap, fallback, or optional all prevent your LCP from being blocked on a font download.
This is the strongest argument against block for body text. It doesn't just look bad, it directly delays the metric your page is measured on.
Cutting the Bytes
WOFF2 Only
web.dev's guidance is to "serve WOFF 2.0 variant to modern browsers," noting WOFF2 gets "up to 30% better compression than WOFF" through built-in Brotli compression.
It advises against EOT and TTF entirely, and treats WOFF 1.0 as needed only for IE11 support specifically. For any modern audience in 2026, WOFF2 alone is the documented recommendation. If your theme is still shipping four format variants per font, you're serving legacy weight for browsers nobody has.
Subsetting and unicode-range
The unicode-range descriptor restricts which Unicode code points a given @font-face rule claims. The browser behaviour is genuinely useful: if the rendered text doesn't use any character in the declared range, that font file is never downloaded at all. If it uses even one character in range, the whole file for that face downloads.
@font-face {
font-family: 'Brand';
src: url('/fonts/brand-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+2000-206F;
}Syntax supports single code points (U+26), ranges (U+0025-00FF), and wildcards (U+4??).
web.dev notes subsetting is "particularly important for Asian languages, where the number of glyphs is much larger than in Western languages," and recommends subsetting by script rather than per-page.
The practical version for most sites: if you ship a font with full Cyrillic and Greek coverage and publish only in English, you're downloading glyphs nobody renders.
What WordPress Gives You
The Font Library shipped in WordPress 6.5 (March 2024). It lets users install and manage fonts directly in the Site Editor, with activated fonts appearing in Global Styles and block typography controls.
The performance and privacy relevance is in how it stores them: fonts are downloaded and stored locally on your WordPress server rather than fetched from a third-party CDN at runtime. That removes a third-party connection from your critical path, and it removes the request that transmits your visitors' IP addresses to an external font host, which has been a documented privacy concern in the EU.
It also lets users disable theme-bundled fonts, which is a genuine performance control. Themes frequently register more font families and weights than a given site actually uses.
Font settings installed this way persist using the same theme.json font-family and font-face schema as theme-declared fonts, then merge into Global Styles.
Worth noting that font management in the editor continued to receive work after 6.5, so if you're documenting specific current capabilities, check against your own install rather than a 2024 announcement.
Resource Hints
If you do load fonts from a third-party host, preconnect establishes the connection early rather than at discovery time.
WordPress has a wp_resource_hints filter that prints dns-prefetch, preconnect, prefetch, and prerender link tags. Important detail: core populates dns-prefetch automatically from the unique hosts of enqueued scripts and styles, but the preconnect array is empty by default. WordPress does not automatically preconnect to any font host. If you want that, you add it through the filter.
The better answer for most sites is to remove the third-party host entirely by self-hosting, which the Font Library now makes straightforward.
A Practical Checklist
- Audit what you're actually loading. Open the network tab, filter to fonts, and count. Sites routinely load six or eight font files when they use two.
- Drop everything but WOFF2 unless you have a documented reason to support IE11.
- Set
font-display: swapon body text, oroptionalif CLS is your failing metric and the fallback is acceptable. - Preload only above-the-fold fonts, with
crossorigin, and verify in DevTools that the font isn't being fetched twice. - Add metric overrides on the fallback to reduce swap-induced layout shift.
- Self-host, using the Font Library or otherwise, removing a third-party connection and a privacy exposure at once.
- Subset by script if you're shipping glyph coverage you never render.
- Measure CLS and LCP in the field afterward, not in a lab run on a fast connection where the font arrives before the block period ends and you see none of the behaviour you're trying to fix.
Important: Test font changes on staging. Metric overrides and
font-display: optionalcan change how your site looks in ways that are easy to miss on a fast connection but obvious to a visitor on a slow one. MagicWP's one-click staging makes that check quick.
Frequently Asked Questions
What is the best font-display value for WordPress?
swap for body text in most cases, since it makes text visible immediately and protects LCP. Use optional if CLS is your problem and the fallback is close enough, since it has no swap period and therefore no swap-induced layout shift. Avoid block for body copy.
Do I need the crossorigin attribute when preloading fonts? Yes, even for fonts on your own domain. web.dev states that "without this attribute, the preloaded font is ignored by the browser," because fonts are always fetched in anonymous-mode CORS and the preload's mode must match. Getting this wrong means downloading the font twice.
Why does my text jump when the page loads?
The fallback font and your web font have different metrics, so when the swap happens the text reflows. Fix it by matching the fallback's metrics using size-adjust, ascent-override, descent-override, and line-gap-override on the fallback's @font-face block.
Do fonts affect Largest Contentful Paint?
Yes, when text is the LCP element. web.dev notes that a font-dependent LCP element introduces a network dependency, and that setting font-display to anything other than auto or block means text stays visible during load.
Should I still serve WOFF and TTF alongside WOFF2? Not for a modern audience. web.dev recommends serving WOFF2 to modern browsers, advises against EOT and TTF, and treats WOFF 1.0 as an IE11 fallback specifically.
Does WordPress have built-in font management? Yes. WordPress 6.5 added the Font Library, which lets you install and manage fonts in the Site Editor, stores them locally on your server rather than fetching from a third-party CDN at runtime, and lets you disable theme-bundled fonts you don't use.
Should I self-host fonts instead of using a font CDN? Generally yes. It removes a third-party connection from your critical path and avoids transmitting visitor IP addresses to an external host, which has been a documented privacy concern in the EU. WordPress's Font Library makes local storage the default path.
How many fonts should a site load? As few as the design genuinely requires. Every family, weight, and style is a separate file. Two families at two weights each is four downloads; the same setup with italics and a light weight quickly becomes eight.
Conclusion
Font loading is a small enough problem to solve completely, which is unusual in performance work. There are five font-display values, one preload attribute people get wrong, four metric override descriptors, and one format worth shipping.
The trade you're managing is invisible text versus shifted text, and the general answer for body copy is swap plus metric overrides on the fallback: visible immediately, minimal shift when the real font arrives. Preload only what's above the fold, with crossorigin, and verify you're not fetching twice.
Then self-host. WordPress's Font Library made that the path of least resistance rather than a project, and it removes a third-party request from your critical path and a privacy exposure from your site in the same move.
Get the best of MagicWP in your inbox.
Monthly engineering notes, product updates, and WordPress performance tips. No spam, unsubscribe anytime.

