You’ve spent weeks on your site. The design looks clean, the copy is tight, and you’re finally ready to run PageSpeed Insights. Then the score comes back: CLS 0.38. You watch the little animation in the report and there it is — your headline snaps sideways the moment your custom font kicks in. The whole layout jolts like someone kicked the table.
I’m Rohan Ratnayake, and I’ve spent the last 5 years as a front-end performance engineer working specifically on Core Web Vitals remediation for e-commerce and content-heavy sites. I’ve sat across from developers who had genuinely beautiful sites that Google was quietly penalizing in rankings because of one overlooked CSS property. The frustrating part? Font-related CLS is one of the most fixable problems in web performance — and still one of the most mishandled.
The hard-way lesson I keep seeing play out: a developer self-hosts a Google Font, celebrates the speed improvement, and ships it. CLS drops from 0.05 to 0.42 overnight. Why? Because when you move away from Google’s CDN, you lose their pre-configured font-display setting. The font blocks rendering or swaps in a way that shoves every line of text 12–18px to the right. I’ve watched that mistake cost a client three weeks of re-testing and a noticeable dip in organic traffic before we traced it back to one missing line in a @font-face declaration.
Here’s what this article gives you: a precise, working system to eliminate font-caused layout shift using font-display and size-adjust — the two properties that actually solve this problem, not mask it.
Why Your Font Is Causing Layout Shift in the First Place

When a browser loads your page, it needs to render text immediately. If your custom font hasn’t arrived yet, it has two choices: show nothing, or show a fallback font. The problem is that your fallback font — usually a system font like Georgia or Arial — has different character widths, line heights, and letter spacing than your custom font.
When the custom font finally arrives and swaps in, all that text reflows. Headlines shrink or expand. Paragraphs shift down. Buttons move. That’s your CLS spike.
The browser handles this swap based on a property called font-display. Most developers either leave it unset or set it to swap without understanding what they’re actually telling the browser to do.
Here’s what each value actually means in practice:
font-display Value | What Happens | CLS Risk |
|---|---|---|
auto | Browser decides — usually blocks rendering | Low CLS, but slow perceived load |
block | Invisible text for up to 3 seconds, then swap | Low CLS, terrible for UX |
swap | Fallback immediately, custom font swaps in | High CLS if fonts differ in size |
fallback | 100ms block, then fallback, 3s for swap | Moderate — good middle ground |
optional | 100ms block, font only loads if already cached | Near-zero CLS — best for CLS score |
swap is what most tutorials recommend. It’s not wrong, but it’s incomplete advice. If your fallback font and your custom font have meaningfully different metrics, swap alone will tank your CLS score. That’s why size-adjust exists.
What size-adjust Actually Does (and Why Nobody Talks About It Properly)
size-adjust is a CSS descriptor you add inside a @font-face rule specifically on your fallback font — not your custom font. It scales that fallback font up or down so it closely matches the physical dimensions of your real font before the swap happens.
When the swap occurs, the layout barely moves because both fonts are now roughly the same size. CLS drops dramatically.
Here’s a basic example:
@font-face{ font-display:swap;
font-family: 'MyFallback';
src: local('Georgia');
size-adjust: 94%;
ascent-override: 90%;
descent-override: 25%;
line-gap-override: 0%;
}
body {
font-family: 'YourCustomFont', 'MyFallback', serif;
}
The size-adjust value isn’t guesswork — you calculate it based on the difference in average character width between your fallback and your custom font. Tools like the Font Style Matcher by Monica Dinculescu let you visually align two fonts side-by-side and find that number precisely.
The Exact Setup That Dropped CLS from 0.41 to 0.04

On a publishing client’s site last year, the homepage had a CLS score of 0.41. Every article page was worse. The culprit was a self-hosted version of Inter swapping in against the default Arial fallback. The weight difference between the two fonts meant the hero headline was jumping 22px horizontally and about 8px vertically on every page load.
Here’s the exact fix we used:
Step 1 — Define a tuned fallback font
@font-face{ font-display:swap;
font-family: 'InterFallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: normal;
}
Step 2 — Set font-display: optional on the custom font
@font-face{
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display:swap;
}
Step 3 — Stack the font-family correctly
body {
font-family: 'Inter', 'InterFallback', sans-serif;
}
The result: CLS dropped from 0.41 to 0.04 within one deploy. No JavaScript, no font loading libraries, no third-party dependencies.
Choosing the Right font-display Value for Your Situation
There’s no single right answer here. The correct value depends on how important the custom font is to your brand versus how important your CLS score is.
| Your Priority | Recommended font-display | Combine With size-adjust? |
|---|---|---|
| Zero layout shift above all else | optional | Not required but still helps |
| Brand typography must show on first view | swap | Yes — critical |
| Balanced UX and performance | fallback | Yes — recommended |
| Font is decorative / rarely seen | optional | Optional |
One thing the generic tutorials skip: font-display: optional tells the browser not to even attempt loading the font if the connection is slow. On a fast connection, the font loads in the background on first visit and is cached for subsequent visits. Users on slow connections never see a layout shift because the custom font just doesn’t load. That trade-off is worth it for most content sites.
If you’re running an e-commerce site where brand consistency on first impression matters more, use fallback combined with size-adjust. You get a very short invisible text period (100ms — basically imperceptible) and a carefully tuned fallback that makes the swap nearly invisible.
How to Find the Right size-adjust Percentage Without Guessing

This is where most developers give up because it feels like trial-and-error. It doesn’t have to be.
Here’s a repeatable process:
- Open the Font Style Matcher tool
- Set the left panel to your fallback font (e.g., Arial)
- Set the right panel to your custom font (e.g., Inter or Playfair Display)
- Adjust font-size on the fallback side until the text blocks align as closely as possible
- The ratio between the two font sizes is your
size-adjustpercentage
For example, if Arial needs to be set to 107% of its base size to match Inter, your size-adjust is 107%.
For the override descriptors (ascent-override, descent-override, line-gap-override), these control vertical spacing. You can fine-tune these in DevTools by toggling the rendered fallback font and watching how much vertical shift occurs. Adjust in 5% increments until the line heights match.
| Descriptor | What It Controls | Start With |
|---|---|---|
size-adjust | Horizontal scaling of the fallback | Match avg char width ratio |
ascent-override | Space above the baseline | Adjust until cap height aligns |
descent-override | Space below the baseline | Usually 20–30% for most fonts |
line-gap-override | Extra spacing built into the font | Set to 0% first, then tune |
What Not to Do (Common Fixes That Don’t Work)
A lot of advice online suggests using font-display: swap and calling it done. That alone doesn’t fix layout shift if your fallback font is measurably different in size. You’ve just replaced invisible text with a visible jump.
Another popular but useless suggestion: preloading your font file with <link rel="preload">. Preloading reduces the time until the font arrives, but it does nothing about the layout shift that happens between fallback and custom font. If the fonts have different metrics, you still get CLS — just slightly sooner.
Here’s a quick breakdown of what actually moves the needle versus what doesn’t:
| Technique | Reduces CLS? | Why |
|---|---|---|
font-display: swap alone | Partially | Reduces invisible text but doesn’t fix metric mismatch |
<link rel="preload"> | No | Loads font faster, shift still happens |
size-adjust on fallback | Yes | Closes the metric gap before the swap |
font-display: optional | Yes | Eliminates the swap entirely on first load |
font-display: fallback + size-adjust | Yes | Best UX/CLS balance |
| Subsetting fonts | Indirectly | Faster load = smaller swap window, but not the root fix |
FAQs
Does size-adjust work in all browsers? As of mid-2023, size-adjust has strong support across Chrome, Edge, Firefox, and Safari (Safari added support in version 17). You can verify current browser support on MDN’s compatibility table. It’s safe to use in production — browsers that don’t support it simply ignore it, falling back to the default font rendering.
My font is loaded via Google Fonts embed. Do I still need to do this? Google Fonts CDN sets font-display: swap by default when you use the standard embed code. That helps with invisible text, but it doesn’t handle the metric mismatch if your chosen Google Font differs significantly from your fallback. You’d still benefit from defining a tuned fallback using size-adjust, especially for large display text where shifts are more visually obvious.
How do I know which element is causing the font CLS? In Chrome DevTools, open the Performance panel, record a page load, and look for Layout Shift events in the timeline. Click on any shift event and DevTools will highlight the exact element responsible. For font-specific shifts, you’ll almost always see it on elements using large or decorative typefaces — headings especially. You can also use the Web Vitals Chrome Extension to see CLS attributed to specific page regions in real time.
Closing Thought
Font-related CLS isn’t a mystery — it’s a metric mismatch problem with a precise solution. The text jumps because your fallback and custom font have different dimensions, and the browser has no instructions to compensate for that. font-display tells the browser when to make the swap. size-adjust tells it how to make that swap invisible.
If your CLS score is above 0.1 and you’re using a custom font, start there. Open the Font Style Matcher, find your size-adjust percentage, define a fallback @font-face block, and set font-display: fallback or optional on your real font. Deploy it. Run PageSpeed Insights again. In most cases, that sequence alone will cut your font-related CLS by 80–90%.
Run that test today — not next sprint. It’s four CSS declarations and it’s the most direct thing you can do right now for your CLS score.

