Font display in Lighthouse: what it flags and how to fix it

Lighthouse 13 now flags fallback too, and swap can still move your page. When a font is worth the work.

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-10-14

Does the font-display warning hurt my score?

Lighthouse flags every web font that loads with font-display: block, auto or fallback. The notice has no weight in your score, but when a headline is your LCP element, hiding it makes LCP late on mobile. Set swap on the fonts visitors see first, and match the fallback font's size with metric overrides, because swap can move the page.

What does the Font display insight check?

In Lighthouse 13 and PageSpeed Insights the insight says: "Consider setting font-display to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with font metric overrides." Before Lighthouse 13 the audit was called "Ensure text remains visible during webfont load".

Lighthouse reads Chrome's own trace of the page load. For every web font the page starts downloading, the trace records the font-display value Chrome used. The insight flags three values:

  • block: the text stays invisible for up to 3 seconds while the font downloads.
  • auto: no value at all. Google Fonts sends that without &display=, and older themes, Font Awesome 4 and Material Icons have it.
  • fallback: the text is invisible for 100 ms only. Lighthouse 13 flags it anyway.

Only two values pass: swap and optional. That changed with Lighthouse 13. The old audit passed block and fallback, so a site that "fixed" this years ago with fallback gets the notice again. WordPress block themes use fallback by default, and Font Awesome 5 and 6 use block.

"Est savings" is the time the font request took in the trace, capped at 3,000 ms. PageSpeed Insights records that trace without throttling, so there you often see a small number. The demo report below was run with DevTools throttling (Slow 4G), where the body font took more than 3 seconds and the table shows the cap. Either way you won't win that time back: with fallback a font hides text for 100 ms at most, and with auto Chrome stops waiting for it 2 seconds after the navigation started.

Lighthouse 13.5.0 report, the Font display insight expanded: Est savings of 3,000 ms, labels FCP and Unscored, two fonts listed: InterVariable.woff2 3,000 ms and BarlowCondensed-Bold.woff2 940 ms

Next to the description Lighthouse shows two labels. FCP is the metric Lighthouse thinks you save on. Unscored means weight 0: the insight itself never moves your score. A block font shows up in your score through Speed Index and LCP.

An @font-face rule nobody uses never triggers it. And because Lighthouse reads the trace, it now also lists fonts you didn't add yourself, such as a Typekit kit or a plugin's Google Fonts file. The old audit printed "unable to check" for fonts from other domains.

What happens in the browser while a web font loads?

The browser can't request a font until it knows a visible element needs it. It needs the HTML, then the CSS, then a style calculation that says "this heading uses Barlow Condensed". Only then does the font request start. On the demo, on a throttled phone, that was at 0.74 s, after the HTML and the CSS were in. A font is always discovered late. A preload only moves that start forward: fonts already get the highest priority.

From that moment the font-display value decides what the visitor sees. These are the timings in the browser source code:

ValueWhat the visitor sees in Chrome
blockInvisible text for up to 3 s from the font request
autoLike block, but never past 2 s after the navigation started. If Chrome rates the connection as 3G or slower, it shows the fallback at once
swapThe fallback right away, the web font when it arrives
fallbackInvisible text for 100 ms; a font that arrives after 3 s is never used on that page
optionalThe fallback right away; the web font only if it is there before the first render

Safari and Firefox also hide text for up to 3 s with block and auto. Firefox waits 1.5 s longer when 75% of the font is already in. Both hide text for 100 ms with fallback and optional, and Firefox does the same with swap.

In Chrome, block is slower than auto: auto has the 2 second cap and block doesn't. And optional in Chrome has no 100 ms grace period. Without a preload the font practically never makes the first render, even from a fast server. With a preload it can, if the font arrives within about 100 ms of the moment the page could render. From the cache on the next page it does.

Demo product page at the first paint with font-display block: header, breadcrumbs and the star rating, a blank gap where the headline goes and nothing under the photo

font-display: block at the first paint

The same page at the first paint with font-display swap: the headline and the description are there in Arial

font-display: swap at the first paint

The demo product page at its first paint, 0.9 s on a throttled phone. The dark box is the photo, still loading in both. Demo product page on corewebvitals.io, Chrome 155, phone 412 px wide, Slow 4G and 4x CPU slowdown.

With block, the first paint shows the header, the breadcrumbs and the star rating, and a blank gap where the product name goes. The headline font is small (15 kB) and arrives at 1.7 s. The body font is 344 kB and still downloading when Chrome stops waiting at 3.7 s, so until then there is nothing under the photo. With swap the product name and the description are readable from the first paint.

The block version at 3 seconds: headline and photo are there, the description is still invisible

With block, at 3 s

The swap version at 3 seconds: headline, photo and description are all there

With swap, at 3 s

The same page at 3 s: the headline font and the photo are in, the body font isn't.

Does font-display affect LCP, CLS and FCP?

LCP

Invisible text can't be the LCP element. With block or auto, a text LCP is reported when the text becomes visible: when the font arrives, or when the block period ends. On a page where the LCP element is a heading or a paragraph, block can add up to 3 seconds to LCP. Chrome adds less with auto, and on the demo's Slow 4G it even showed that text in Arial right away. With swap, the LCP is the paint in the fallback font, and the change to the web font doesn't add a new LCP entry, even when the web font text is larger.

On the demo the photo is the LCP element in every version, so font-display didn't move LCP there. That is the first thing to check on your own pages: is the LCP element text or an image? The LCP breakdown insight in the same Lighthouse report names it.

CLS

Every swap that changes how much room the text takes moves everything after it. That happens with block and auto too: invisible text takes the room of the fallback font, and the swap to the web font is the same jump. On the demo the block, auto and swap versions all scored CLS 0.031, at the moment the headline font arrived.

Demo page with font-display swap before the headline font arrives: the headline takes three lines in Arial

With swap, before: three lines in Arial

The same page after the headline font arrived: the headline takes two lines and the photo moved up, the dashed outline shows where it was

With swap, after: two lines

With plain swap the headline goes from three lines to two when Barlow Condensed arrives at 1.7 s. Everything under it moves up 39 px: CLS 0.031. The dashed outline is where the photo was.

Demo page with metric overrides before the headline font arrives: the fallback headline takes two lines

Metric overrides, before: two lines in Arial Bold

The same page after the headline font arrived: two lines, nothing moved

Metric overrides, after: still two lines

The same page with a fallback face that scales Arial Bold to Barlow's width (size-adjust: 68.32%). The headline takes two lines from the first paint and nothing moves: CLS 0.

So swap passes the Font display insight and keeps the layout shift. 0.031 is below 0.1, but on a real product page a second font arriving later, a price in another font or a sticky bar can add up. Metric overrides on the fallback stop that shift.

Lighthouse won't point you to the font. On the swap version, Layout shift culprits blames the photo, as an "Unsized image element". It has a width and a height. What moved it was the headline font, arriving at that exact moment. Check the time of a shift against your font requests before you go looking for dimensions that are already there.

Lighthouse Layout shift culprits on the swap version: total 0.031, the hero image listed with the cause Unsized image element, although the img tag has width 800 and height 600

FCP

Lighthouse reports the savings as FCP. On most pages something else decides FCP: on the demo the header and the shipping bar are in a system font and paint at 0.8 s with block and with swap alike. In my own test with nothing but web font text on the page, Chrome 155 still reported FCP at 112 ms while the text was invisible. Chrome counts the text as painted, it only skips drawing the glyphs. So the time Lighthouse shows as FCP savings will not show up in your FCP. What you lose with block is the LCP of a text element, Speed Index, and what the visitor sees.

How many sites get the notice, and when can you ignore it?

State of CWV measures the @font-face rules on the home pages of 170,706 sites that declare web fonts. Half of those rules would trigger the insight if the font loads: 39.7% have no value (auto), 9.6% use block and 0.9% fallback. Only 471 sites use the optional value anywhere.

Bar chart: share of sites with a good LCP on phones by number of font files and font-display mix

Sites with a good LCP on phones, grouped by how many font files the home page loads and by the font-display values in its @font-face rules. State of CWV, October 2026: 170,706 sites that declare web fonts, CrUX p75 on phones, good LCP is 2.5 s or less.

With one or two font files it makes almost no difference: 88% of the sites with only swap or optional have a good LCP on phones, against 87% of the sites with only block, auto or fallback. With six to ten font files the gap is 6 points, with eleven or more it is 9. The sites that mix both kinds do worst. They declare far more fonts, 44 @font-face rules at the median against 18, which looks like a theme font plus a plugin font plus an icon font. CLS hardly differs between the groups.

That is a correlation. Sites that look after their fonts usually look after everything else too. Add the browser behaviour above, and my rule is: cut font files first, then set font-display.

You can leave the notice alone when:

  • the font only renders text below the fold, such as the footer or the reviews;
  • your LCP element is an image and the font causes no shift.

Can you change font-display on every font?

Not always. The font URL in the Lighthouse table tells you where it comes from, and that decides where you set swap, or whether you can:

  • Your own @font-face (your own domain): add font-display: swap; inside each rule. The exception is an icon font, see below.
  • Google Fonts (fonts.gstatic.com): add &display=swap to the fonts.googleapis.com/css2 link that loads it. If you host the font yourself, it is your own @font-face and the first line applies.
  • Adobe Fonts (use.typekit.net): open the web project on fonts.adobe.com, choose Edit Project and set font-display in the sidebar. Adobe's default is auto, and a parameter in the URL does nothing.
  • A WordPress theme (/wp-content/themes/): in a block theme one setting per font in theme.json, in a classic theme the theme's own @font-face or its Google Fonts link (see WordPress below).
  • A WordPress plugin or page builder (/wp-content/plugins/): a setting if the plugin has one (Elementor and OMGF have one for Google Fonts, see WordPress below). Without one, only its developer can change it.
  • A Shopify theme (your shop's domain): one parameter in theme.liquid (see Shopify below). Dawn already sets it.
  • A Shopify app (the app's domain): only the app's developer can change it, so ask them.
  • An icon font (fa-solid-900.woff2, bootstrap-icons.woff2, eicons.woff2): with swap it shows letters or empty boxes where the icons go. Replace icon fonts with SVG when you can, otherwise leave them on block.
  • A third-party script that adds a font to your page (the vendor's domain): out of your hands. Load the script after the page has rendered, or leave the line alone.

How do you see font problems in your field data?

Lighthouse gives you one load on one simulated phone. Your visitors have every connection there is, and a returning visitor has the font in the cache. In CoreDash two breakdowns tell you whether fonts cost you anything.

  • LCP by element. If the LCP element on mobile is a heading or a paragraph, a font on block or auto delays it on every visit that waits for the font. If it is an image, the font doesn't touch your LCP.
  • CLS by element. A font swap moves what sits below the text: the photo, the next section, the buy button. CoreDash names the element that moved. It won't say a font did it: if the element that moves sits right under a heading or a paragraph and has a width and a height, a font swap is the likely cause.

CrUX gives you the same metrics at p75 for your origin or a URL, for the last 28 days, without the attribution.

CoreDash table LCP by attribution element: h1.product-title on top with a p75 LCP of 3.28 s rated improve, the product photo img.hero second at 2.21 s rated good

LCP by element

CoreDash table CLS by attribution element, sorted by sessions: the product photo img.hero on top with a p75 CLS of 0.07 rated good, the navigation second at 0

CLS by element

CoreDash on a demo site, acme-pagespeed.com, mobile, p75. The product title is the LCP element on more sessions than any other element, with an LCP of 3.28 s. When the product photo is the LCP element, LCP is 2.21 s. In the CLS table that photo, right under the title, is the top row: 0.07 on 1,214 sessions. Still rated good, but no other element moves on as many sessions.

How do you find the flagged font in DevTools?

Open the page in Chrome, go to the Performance panel, set the network to Slow 4G and the CPU to 4x slowdown, and record a reload. The Insights sidebar lists Font display with every flagged font. Click it and DevTools outlines the font requests in the Network track.

Chrome DevTools Performance panel with the Font display insight open: InterVariable 3.00 s and BarlowCondensed-Bold 1.01 s, both font requests outlined in red in the Network track, the LCP marker at 2.56 s

Then look where each font request ends, where the LCP marker is, and whether the Layout shifts track has a shift right after a font ends.

To find every @font-face that points to a flagged file, open Sources, search all files (Ctrl+Shift+F, Cmd+Option+F on a Mac) for the file name, and set the value in each rule you find.

How do you fix the Font display insight?

Your own @font-face

Add font-display: swap to every @font-face rule for that font, including the copies in a second stylesheet:

@font-face {
  font-family: "Barlow Condensed";
  src: url(/fonts/barlow-condensed-700.woff2) format("woff2");
  font-weight: 700;
  font-display: swap;
}

Google Fonts

Add &display=swap to the URL:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@700&display=swap">

Faster still is to host the font yourself: no extra connection, and you control the preload.

Load fewer font files

Drop the weights and styles you don't use, and use one variable font instead of six static files. Every font file you remove is one less request, one less swap and one less line in this insight.

Stop the layout shift with metric overrides

Give the fallback font the size of the web font. You do that with a second @font-face that points to a local font and scales it:

@font-face {
  font-family: "Barlow Fallback Arial";
  src: local("Arial Bold"), local("Arial-BoldMT");
  font-weight: 700;
  size-adjust: 68.32%;
  ascent-override: 146.37%;
  descent-override: 29.27%;
  line-gap-override: 0%;
}
h1 {
  font-family: "Barlow Condensed", "Barlow Fallback Arial", Arial, sans-serif;
}

size-adjust is the width of your text in the web font divided by its width in the fallback. ascent-override and descent-override are the web font's own ascent and descent divided by that same size-adjust. Tools like Fontaine and Capsize calculate them from the font files.

The same goes for the body font, and there you measure on your own text, at phone width and at the size you use. Inter changes its width with the font size: from about 98% of Arial at headline sizes to about 105% at 18 px. On the demo a size-adjust of 97.6% wrapped the product description to five lines in the fallback against six in Inter, and the buy button still moved. At 101% both wrap the same. To check it, block the font file in DevTools (Network, right click, Block request URL), reload at phone width and compare the line breaks with a normal load. Change size-adjust until they match.

local() only finds fonts installed on the device: Arial is on Windows, macOS and iPhones, Android phones have Roboto. For Android visitors add a second fallback face for Roboto with its own numbers:

h1 {
  font-family: "Barlow Condensed", "Barlow Fallback Arial", "Barlow Fallback Roboto", sans-serif;
}

local() skips a face whose font isn't installed, so each device takes the first one it has. Safari supports size-adjust but not yet the ascent and descent overrides, so the width matches there, and with line-height: normal the line height can differ a little. WordPress and Shopify have no setting for any of this. It takes a developer.

swap or optional: which one?

swapoptional
Invisible textNoNo
Layout shiftYes, unless the fallback is matchedNo
Web font on a slow first visitYes, lateNo, the fallback stays for that page
Web font on the next pageYes, from the cacheYes, from the cache

Use swap for the fonts that carry the brand, such as the headline and the logo, with a matched fallback. Use optional for body text when the system font is fine too, and preload that font so it has a chance on the first page:

<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face {
  font-family: "Inter";
  src: url(/fonts/inter-var.woff2) format("woff2");
  font-weight: 100 900;
  font-display: optional;
}
</style>

The crossorigin attribute is required, even for a font on your own domain: without it the browser downloads the font twice. A font preload in Chrome can hold the first render back by up to 100 ms, so preload one or two fonts, never all of them. On the demo's Slow 4G the preloaded fonts still arrived after the first paint, and the page kept the fallback.

WordPress

In a block theme, set fontDisplay per font face in theme.json:

"fontFace": [{
  "fontFamily": "Barlow Condensed",
  "fontWeight": "700",
  "fontStyle": "normal",
  "fontDisplay": "swap",
  "src": ["file:./assets/fonts/barlow-condensed-700.woff2"]
}]

In a classic theme, add &display=swap to the Google Fonts URL in wp_enqueue_style, or put font-display in the theme's own @font-face rules. In Elementor the setting is under Elementor, Editor, Settings, on the Advanced tab: Google Fonts Load. OMGF hosts Google Fonts locally and can force swap on them. The WordPress guide has the rest of the font and CSS work.

Shopify

In theme.liquid, theme fonts come in through the font_face filter. Without font_display it writes no font-display:

{{ settings.type_header_font | font_face: font_display: 'swap' }}

Next.js

next/font uses display: 'swap' by default, preloads the font and creates a fallback with metric overrides (adjustFontFallback). If Lighthouse still flags a font on a Next.js site, it was loaded another way: a <link> to Google Fonts in the layout, or a CSS file from a component library.

How do you check that the fix worked?

Run Lighthouse again on mobile: Font display moves to the passed insights. On the demo the score went from 97 to 98, all through the scored metrics: Speed Index from 1.9 s to 1.3 s because the text shows earlier, and CLS from 0.031 to 0 with the overrides. Then look at CLS in your own report: if the page still moves when a font arrives, the fallback needs metric overrides.

Lighthouse report with font-display block: score 97, Speed Index 1.9 s, CLS 0.031, Font display with Est savings of 3,000 ms

Before: font-display: block

Lighthouse report with swap and metric overrides: score 98, Speed Index 1.3 s, CLS 0, Font display passed

After: swap with metric overrides

Lighthouse 13.5.0 on the demo, mobile, DevTools throttling.

If you chose optional, the Lighthouse screenshot usually shows the fallback font. Lighthouse loads the page cold, and that is optional doing its job.

CoreDash shows the effect per device from the next visit onwards. CrUX and Search Console show the last 28 days, so two weeks after the fix half of what they show is still the old site.

Want to know if a font is what keeps your mobile LCP or CLS red? In a Core Web Vitals power hour we open your field data and a trace of your page together, and you leave knowing which font rule to change in your theme.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

 

CoreDash has MCP built in.

Connect it to Claude or any AI agent. Ask it why your INP spiked last Tuesday.

See how it works
Font display in Lighthouse: what it flags and how to fix it Core Web Vitals Font display in Lighthouse: what it flags and how to fix it