The Font Arrives Late and the Text Jumps: Swap Does That by Design
Updated October 2026
font-display: swap appears on 50.1% of mobile pages that use web fonts, per the HTTP Archive Web Almanac’s 2025 fonts chapter. The two values that put any limit on that swap, fallback and optional, together reach 5.6%.
So the setting almost everyone turns on is the one that promises to replace your fallback face whenever the real font turns up, however late. Invisible text and shifted text look like one problem with one switch. They come from different halves of the font-loading path, and the display value governs only one of them.
Quick Summary: Which Symptom You Actually Have
| If you see this | Do this | Why |
|---|---|---|
| Blank space where text should be, then text appears | Check whether the display value reached that font at all | A blocking value, or a rewrite that never touched your @font-face |
| Text at once, typeface changes later, nothing moves | Nothing | swap behaving exactly as specified |
| Text at once, typeface changes later, lines move | Match the fallback’s metrics, not the display value | The two faces measure differently, so the swap reflows the paragraph |
| Text at once, typeface never changes | Confirm the font actually loaded | optional declined it, or the request failed |
Two Variables Tell You Which One It Is
Throttle the network to Slow 3G in your browser’s dev tools, reload, and watch only the first screen. Record two things: whether the text is there on the first paint, and whether it moves afterwards.

What Each font-display Value Actually Promises
| Value | Block period | Swap period | What the visitor gets | Share of mobile pages, 2025 |
|---|---|---|---|---|
auto | the browser’s choice | the browser’s choice | invisible text first | 9.1% |
block | about 3s | infinite | invisible, then swaps late | 24.9% |
swap | about 100ms | infinite | fallback, then swaps late | 50.1% |
fallback | about 100ms | about 3s | fallback, swaps only if quick | 5.1% |
optional | about 100ms | zero | fallback, and it keeps it | 0.5% |
Those durations are the recommendations written into CSS Fonts Module Level 4, which also notes that user agents may use slightly different ones. The adoption column is the Web Almanac’s 2025 crawl.

An infinite swap period is the entire story behind a late reflow. block and swap both carry one, so both will replace your fallback at second nine if second nine is when the file arrives. fallback stops asking after about three seconds. optional never swaps at all unless the font was already cached, which makes it the only value that cannot move a paragraph after the reader started reading it.
Why the Toggle Did Nothing to Half Your Fonts
Our FontsModule.php in the plugin trunk appends display=swap to Google Fonts stylesheet URLs, rewrites a blocking display=auto or display=block, and leaves a deliberate fallback or optional alone. (xSpeed Cache is ours, built by WPDeveloper, a Startise company.) It does that through a filter on style_loader_tag, the hook WordPress runs over stylesheets it enqueued itself. That last detail is the whole problem, and it is true of every plugin in this genre.
| Where the font comes from | Reached by a display-value toggle |
|---|---|
| A Google Fonts stylesheet enqueued through WordPress | ✅ |
A <link> the theme prints straight into header.php | ❌ |
A font pulled in by an @import inside the theme’s CSS | ❌ |
A self-hosted @font-face rule in the theme’s stylesheet | ❌ |
Self-hosted faces declare their own font-display inside the @font-face block, so that is where you set it for those, and no URL rewrite can do it for you. Our fonts documentation states this directly. Competitors’ release notes carry the same shape: LiteSpeed Cache 7.9 records resolving an HTML attribute parse problem in its Font Display setting, and WP-Optimize’s changelog added an option in 3.5.0 to switch Google Fonts processing off entirely when it conflicts with another plugin.
A Fonts panel also governs only how the font renders, never whether the stylesheet referencing it blocks the page, which is the separate job of CSS and JavaScript loading and critical CSS.
The Shift Is a Metrics Problem, So Fix the Metrics
If the verdict was “text at once, then it moves”, the display value is doing its job and the fallback is the wrong size. A swap from a fallback with a taller x-height or wider advances re-wraps the line, and every line below it moves.
CSS has four descriptors for exactly this, documented in Chrome’s “Improved font fallbacks” by Katie Hempenius: size-adjust, ascent-override, descent-override and line-gap-override. You declare a second @font-face pointing at a local system font, override its metrics to match your web font, then name that face as the fallback. The swap changes the shapes without changing the measurements, and the shift goes to zero rather than getting smaller.
People skip it because a display value is a checkbox and a metric override is four lines of CSS. Fonts also sit inside the first-paint window, which is why caching alone never moved your LCP and why page weight and LCP come apart on font-heavy themes.
Preload Buys One Face and Charges You for the Rest
crossoriginis mandatory. MDN’srel=preloadreference states that font preloading requires it. Without it the file is fetched in the wrong mode, the CSS-driven request cannot reuse it, and the font downloads twice.- Preload only what paints above the fold. A preloaded font is fetched at high priority whether or not the first screen needs it, so a full stack competes with your hero image for the same bandwidth. One or two faces is usually right.
Checking This on a Site You Did Not Build
Read the rendered page, not the settings screen. A toggle reporting “on” tells you what the plugin intends, not what reached the markup, and the three unreachable rows above are where they differ.
Run the free scan on your own site: xspeedcache.com/scan/
Our font and resource-hint controls are free, and the self-hosting and subsetting that remove the third-party connection are what the paid tier adds. Autoptimize and WP-Optimize both handle Google Fonts well if that one piece is all you need.
Frequently Asked Questions
I turned on font-display swap and my text still goes invisible. Why?
The rewrite only reaches fonts WordPress enqueued as stylesheets. A <link> hardcoded in header.php, an @import, or a self-hosted @font-face never passes through it.
I fixed the invisible text and now the page jumps instead. Did I make it worse?
You traded one symptom for the other, which is what the specification says those values do. The jump is a metric mismatch between your fallback and your web font, so size-adjust on a fallback @font-face is the fix, not reverting the display value.
My CLS score got worse after enabling font optimization. Is that expected?
It is consistent: swap converts a paint delay into a swap that can land at any moment, and a swap between differently measured faces is a layout shift. Override the fallback metrics and the score recovers.
Should I just use font-display: optional?
It is the only value that cannot shift text after first paint, and it is on 0.5% of pages because visitors on slow connections may then never see your typeface. For body text, fallback with matched metrics is the usual compromise.
My preloaded font downloads twice. What did I get wrong?
Almost certainly a missing crossorigin attribute on the <link rel="preload">. Fonts are fetched over CORS, so a preload without it is a different request from the one the CSS makes.
The Order to Fix It In
What to do this week:
- Throttle to Slow 3G and record the two variables, so you know which verdict you have.
- Fetch the rendered HTML and check which font sources carry a display value.
- Set
font-displayinside your self-hosted@font-faceblocks, where no toggle reaches. - Add a metric-matched fallback face with
size-adjustif anything moved. - Cut your preload list to the faces that paint above the fold, each with
crossorigin.
For the toggles rather than the CSS, xSpeed Cache does the Google Fonts rewrite, the preload emission and the preconnect free, with self-hosting and subsetting on the $29/yr founding price and a 14-day money-back guarantee: pricing. Stripping unused CSS changes which fonts the first screen asks for, so run the two-variable read again afterwards. The font arriving late is not the bug. The paragraph that moves when it arrives is.