Fix layout shift caused by web fonts

Text that jumps when a web font loads. How to confirm fonts are the cause, work out fallback font overrides, and choose font-display so the swap moves nothing.

AdvancedTroubleshooting7 min readUpdated October 30, 2026

In this guide
  1. What happens at the swap
  2. Step 1: confirm that fonts are the cause
  3. Step 2: shrink the window for the swap
  4. Step 3: choose a font-display value
  5. Step 4: make the fallback take the same space
    1. Where the numbers come from
    2. Choosing the local font
    3. Check the metrics for each system
    4. Browser support
  6. Step 5: check the result
  7. Other font problems that look like this
  8. Check your own page
  9. Sources

What happens at the swap

Until a web font arrives, the browser draws text in a fallback font, or in invisible text that takes up the space of the fallback. When the web font is ready, the browser swaps it in. If the two fonts have different proportions, the page changes in two ways:

Either change pushes everything below the text up or down. That is a layout shift, and it counts towards Cumulative Layout Shift.

Hiding the text does not remove the problem. MDN describes the block period as rendering an invisible fallback face. The invisible text still takes up room, so the swap moves the page when the real font arrives. The web fonts guide covers loading fonts well; this guide is about the shift.

Step 1: confirm that fonts are the cause

  1. Check the Lighthouse report. The insight called Layout shift culprits lists the biggest shifts and, for each, the reasons it can recognize. One of them is labelled Web font. Lighthouse blames a font when a font request happened shortly before a paint in which the layout shifted. Other labels are an injected iframe, an animation and an unsized image.
  2. Watch the shift happen. In Chrome DevTools open the Rendering tab, tick Layout Shift Regions and reload. Areas that move are briefly highlighted in purple. If the highlight covers paragraphs and headings when the text changes face, fonts are the cause.
  3. Remove the font and compare. In DevTools open the Command Menu (Ctrl+Shift+P or Cmd+Shift+P), type block, choose Show Request Blocking and add the address of your font file as a pattern. Reload. The page now stays in the fallback font. If the shift disappears, the swap was the cause, and the fallback you see is the one you need to match.
  4. Test on a slow connection. The swap only happens when the font is not yet available, so on a fast connection with a warm cache you may not see it. Tick Disable cache and throttle the network.

Fonts tend to hurt first-time visitors most. Returning visitors usually have the font cached, so the swap is rare for them.

Step 2: shrink the window for the swap

The shorter the time between the first paint and the font arriving, the less visitors notice, and the more often the font is ready before the first paint.

Step 3: choose a font-display value

font-display decides what happens while the font loads.

ValueEffect on layout shift
swapText shows at once in the fallback and swaps when the font arrives. The swap can shift the page unless the fallback matches
optionalExtremely small block period and no swap period. If the font is not ready almost immediately, the page stays in the fallback for the whole visit. Nothing swaps, so nothing shifts
fallbackA short swap period. A late font is not swapped in, which limits shifts, but the text may still move if the font arrives inside the short period
blockInvisible text first, then a swap. The page still shifts when the font arrives
autoThe browser decides

optional is the safest value for layout stability, with a price: a first-time visitor on a slow connection may not see your font on that first page, and it can be used from the next page on, once it is cached. Pair it with a preload so the font is ready in time for most visits. Lighthouse's Font display insight points to swap or optional and adds that swap can be improved with font metric overrides, which is the next step.

Step 4: make the fallback take the same space

If you want the swap, give the fallback the same dimensions as the web font. CSS has four descriptors for this on @font-face:

You use them on a new @font-face whose source is a font already on the visitor's device:

@font-face {
  font-family: "Brand Fallback";
  src: local("Arial");
  size-adjust: 104%;
  ascent-override: 91.35%;
  descent-override: 24.04%;
  line-gap-override: 0%;
}

body { font-family: "Brand", "Brand Fallback", sans-serif; }

The text is first drawn in the adjusted Arial, which takes the same room as "Brand". When "Brand" arrives, the lines break in the same places and have the same height.

Where the numbers come from

The Chrome team gives these equations. The metrics belong to the web font, and the average character width is needed for both fonts:

size-adjust       = avgCharWidth(web font) / avgCharWidth(fallback)
ascent-override   = ascent   / (unitsPerEm × size-adjust)
descent-override  = descent  / (unitsPerEm × size-adjust)
line-gap-override = lineGap  / (unitsPerEm × size-adjust)

For example, say the web font has 1,000 units per em, an ascent of 950, a descent of 250 and no line gap, and its average character width is 520. Say the local Arial has an average width of 500. Then size-adjust is 520 ÷ 500 = 104%. The ascent override is 950 ÷ (1,000 × 1.04) = 91.35%, and the descent override is 250 ÷ 1,040 = 24.04%. These numbers are invented to show the arithmetic, which is why the code above uses them. Yours come from your font.

Where to get the inputs:

If you only want a smaller change, use the ascent, descent and line gap overrides on their own, set to the web font's metrics divided by its units per em. The Chrome team says this is simpler and usually enough to reduce the shifts noticeably. Adding size-adjust is what can remove them.

Choosing the local font

Arial is the usual choice for a sans-serif web font and Times New Roman for a serif, but neither is on Android, where Roboto is the system font. The numbers change with the fallback font, because the average width differs. For wide coverage, declare one adjusted fallback for Windows and Mac, a second for Android and a generic family at the end:

body { font-family: "Brand", "Brand Fallback", "Brand Fallback Android", sans-serif; }

Do not fix this with a fixed line-height. The Chrome team advises against it. line-height sets the height of the line box and does nothing about the width of the letters or the mismatch of the fonts themselves.

Check the metrics for each system

Browsers on different systems read different tables from the font: hhea, typo or win. For most fonts, about 90% of those on Google Fonts according to Chrome, the values come out the same whichever table is used, so one set of overrides works everywhere. A font that gives different line heights on Windows and on Mac needs separate styles per system, which is only worth it if you can serve them.

Browser support

size-adjust works in Chrome 92 and later, Firefox 92 and later, and Safari 17 and later. The three vertical overrides work in Chrome 87 and Firefox 89 and are in preview in Safari at the time of writing. A browser that does not support a descriptor ignores it, so the worst case is the fallback as it is today.

Step 5: check the result

  1. Run the same test as in step 1: Layout Shift Regions with the cache disabled and the network throttled.
  2. Compare the first screen before and after the font arrives. The text should not re-wrap and the elements below should not move.
  3. Test on Windows, on a Mac and on an Android phone, because the local fonts differ.
  4. Watch the real-visitor CLS over the next weeks. See how to fix CLS for the other causes.

Other font problems that look like this

Check your own page

Run the free audit and look at the Layout shift culprits and Font display insights. If a web font is named as a culprit, work through steps 2 to 4 for the fonts used above the fold first.

Sources

Start here

Free website audit

Live Google data for any public page: mobile and desktop.

Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it

Rather have this done for you? Speed optimization · Book a call

Related guides

All guides →