Reduce CLS from font swapping
Use @font-face descriptors (size-adjust, ascent-override, descent-override, line-gap-override) on fallback fonts to match custom web font metrics and reduce layout shift during font swapping. This is an advanced technique for use after addressing more common CLS causes like missing image dimensions.
When you use font-display: swap in your @font-face declaration, the browser renders text immediately with a fallback system font, such as Arial or Times New Roman, instead of hiding it while the web font downloads. After the web font downloads, typically 100 to 500 ms after first paint, the browser swaps it in.
Shopify's font_face filter doesn't add a font-display descriptor unless you pass one explicitly. Without it, browsers default to font-display: auto, which in Chrome behaves like block. Text is invisible for up to 3 seconds while the font loads (a Flash of Invisible Text, or FOIT). To get the swap behavior that this technique addresses, add font_display: 'swap' to your font_face call:
If the web font has different character widths, line heights, or spacing than the fallback, then all text reflows at once, shifting surrounding content. This shift is recorded as CLS. On text-heavy pages, the cumulative shift from font swap can be 0.05 to 0.15 CLS units.
Anchor to Set an explicit line-height firstSet an explicit line-height first
Before you measure font metrics, set a unitless line-height on your text. A unitless value computes to a multiple of font-size, so the line box height doesn't depend on the font's own metrics and the swap can't change it:
With the default line-height: normal, the line box comes from the font's ascent, descent, and line gap, so a swap between two fonts with different metrics changes the height of every line. That difference is proportional to font-size, which means headings shift more than body text does.
This handles the vertical half of the problem only. Different glyph widths can still change where lines wrap, which changes the line count and moves the content below. Use size-adjust for the width difference, and an explicit line-height for the height difference.
Anchor to Match fallback metrics to the web fontMatch fallback metrics to the web font
Use modern CSS @font-face descriptors to adjust a local fallback font to more closely match the metrics of your web font, minimizing the visual shift when the swap occurs.
@font-face descriptors for fallback fonts:
size-adjust: scales the glyphs of the fallback font.ascent-override: matches the height above the font's baseline.descent-override: matches the depth below the font's baseline.line-gap-override: matches the line spacing.
Anchor to Common fallback pairingsCommon fallback pairings
The following table lists common Shopify theme fonts and their closest system font fallbacks.
| Web font | Closest system fallback | Notes |
|---|---|---|
| Futura | Trebuchet MS | Geometric sans-serif, similar x-height. |
| Helvetica Neue | Arial | Near-identical metrics on most platforms. |
| Playfair Display | Georgia | Serif with similar proportions. |
| Montserrat | Verdana | Wide geometric sans-serif. |
| Lato | Tahoma | Humanist sans with similar metrics. |
There are no universal values. The correct size-adjust, ascent-override, descent-override, and line-gap-override values depend on the specific web font, the specific fallback font, and the weights and styles you use, so you have to measure them per font pair.
To measure them, read the metrics out of each font file and compute the ratios:
- Get
unitsPerEm,ascender,descender,lineGap, and the x-height or average character width for both the web font and the fallback. Font metric databases and open source tools such as Fontaine and@capsizecss/metricsexpose these values, and Fontaine can generate the fallback@font-facerule for you. size-adjustis the ratio of the two fonts' average character widths, expressed as a percentage.ascent-override,descent-override, andline-gap-overrideare the web font'sascender,descender, andlineGapdivided by itsunitsPerEm, then divided again bysize-adjust, each expressed as a percentage.
Then verify by measuring: render the same block of text with the fallback and with the web font, and compare the rendered heights and widths in the Elements panel until they match.
Shipping a smaller font file shortens the window before the swap, but it doesn't change the CLS that the swap causes: the shift comes from the metric mismatch, not from the download time. Custom fonts that you upload to the theme's assets/ directory are served byte for byte as uploaded, so subset them yourself, to the character sets your store actually renders, before you upload them.
Anchor to ExamplesExamples
Tooling:
- Fallback font generators: Fontaine generates fallback
@font-facerules from a font file, and Capsize exposes the underlying metrics if you want to compute the ratios yourself. - Chrome DevTools: use the Elements panel Computed tab to compare the rendered dimensions of text with the fallback font and with the web font. This helps you visually assess how close your override values are.
- Network throttling: use browser developer tools to slow down the network, which makes the font swap easier to see.
When to prioritize:
- After you've already addressed other CLS sources, such as images without explicit dimensions and injected content without reserved space.
- If RUM data shows that font swapping is a primary contributor to your CLS.
- This is easier to implement in a custom theme than in a theme from the Shopify Theme Store.
- If your theme uses Shopify system fonts, this technique isn't needed because no font swap occurs. See Eliminate font loading delays with system fonts.
Anchor to TestingTesting
- Use Chrome DevTools Network panel with throttling to visually inspect the font swap and measure CLS.
- Use the Chrome DevTools Performance panel to record a trace and analyze layout shift events.
- Compare CLS scores from lab tests before and after the change.
Anchor to ReferencesReferences
- How to optimize Cumulative Layout Shift (CLS) on Shopify sites
- Self-host web fonts on Shopify CDN
- Prevent image layout shift
- Reserve space for app-injected content