FlowingDev

Font Fallbacks, explained: dodging the CLS layout earthquake

Learn how CSS font metrics like size-adjust and ascent-override prevent Cumulative Layout Shift by making system fonts match your web fonts perfectly.

Try the tool: Font Fallback Generator (CLS)

In one sentence

Font fallback metrics are a set of CSS superpowers that artificially stretch and squash a system font so it takes up the exact same mathematical space as your custom web font, preventing the page layout from violently shifting when the custom font finally loads.

The problem it solves

To understand the problem, you first have to understand the web's oldest typographic war: what do we show the user while waiting for a 150KB custom web font to download over a spotty 3G connection?

Historically, browsers took one of two approaches, and they were both terrible. The first was FOIT (Flash of Invisible Text), where the browser simply hid the text until the font downloaded. Users would stare at a blank screen, wondering if your site was broken. The second was FOUT (Flash of Unstyled Text), where the browser immediately rendered the text using a local system font (like Arial or Times New Roman), and then hastily swapped it out for your gorgeous custom font the millisecond it arrived.

The web development community largely agreed that FOUT is better for accessibility and perceived performance. After all, reading ugly text is better than reading no text. We formalized this with the CSS rule font-display: swap.

But font-display: swap birthed a new monster: the Cumulative Layout Shift (CLS).

Every font has a unique skeleton. An "x" in Roboto is physically wider than an "x" in Arial. The height of the capital letters varies. The spacing built into the font files is completely different. When the browser swaps Arial out for Roboto, the text suddenly takes up a different amount of horizontal and vertical space. Paragraphs reflow. Text wraps to a new line.

If this happens while the user is already reading or about to click a button, the entire page jumps like an earthquake just hit the DOM. Buttons move out from under the user's cursor. Images get pushed down the page. Google introduced the Core Web Vitals metrics to measure this exact frustration, heavily penalizing the SEO of sites with high CLS. We needed a way to make the ugly temporary system font perfectly mimic the physical footprint of the beautiful custom font.

How it works under the hood

To stop the layout shift, we don't change the custom web font. Instead, we create a "Frankenstein" version of a local system font using the @font-face rule. We use specialized CSS descriptors to override the system font's internal geometry.

The Anatomy of a Font Box

To manipulate a font, you need to understand how a browser calculates the box around a line of text. It's not just about the font-size. It relies on three internal metrics baked into the font file itself:

  1. Ascent: The distance from the invisible baseline (where the letters sit) to the top of the tallest characters (like the top of an 'h' or 'T').
  2. Descent: The distance from the baseline to the bottom of the lowest hanging characters (like the tail of a 'y', 'g', or 'p').
  3. Line Gap: The recommended empty space between the descent of one line and the ascent of the next line (often called leading).

In CSS, the total height of a line of text is roughly ascent + descent + line gap. If your custom font and your system font have different values for these three metrics, your layout will jump vertically. If the letters are different widths, your layout will jump horizontally.

Enter the Overrides

CSS Fonts Module Level 4 introduced four magical descriptors that can be applied inside a @font-face block. These descriptors intercept the browser's reading of the font file's internal metrics and overwrite them on the fly.

  • size-adjust: This acts like a multiplier for the entire font. If your custom font is generally wider than Arial, you might set size-adjust: 110% on Arial to stretch it horizontally.
  • ascent-override: Overwrites the font's internal ascent metric.
  • descent-override: Overwrites the font's internal descent metric.
  • line-gap-override: Overwrites the font's internal line gap metric.

Putting the Math Together

The trick is that size-adjust scales everything—including the horizontal widths, the ascent, and the descent. So, if you scale a system font up by 110% to make the line widths match your web font, you have inadvertently made the vertical height of the system font 110% taller, which causes a vertical layout shift!

To fix this, the overrides (ascent-override, descent-override, line-gap-override) are calculated as percentages of the size-adjust value, not the original font size. You use size-adjust to perfectly match the horizontal width, and then you use the override metrics to essentially squish the vertical height back down (or pull it up) to match your custom font's exact bounding box.

Here is what the resulting code looks like. Notice how we create a brand new, imaginary font family called Roboto-Fallback that wraps the local Arial font with mathematically precise overrides:

/* 1. Load your actual web font */
@font-face {
  font-family: 'Roboto';
  src: url('/fonts/roboto.woff2') format('woff2');
  font-display: swap;
}

/* 2. Create the Frankenstein fallback */
@font-face {
  font-family: 'Roboto-Fallback';
  src: local('Arial');
  /* Stretch Arial horizontally to match Roboto */
  size-adjust: 104.5%; 
  /* Squish Arial's vertical metrics to match Roboto */
  ascent-override: 89.92%;
  descent-override: 23.44%;
  line-gap-override: 0%;
}

/* 3. Use them together in your CSS */
body {
  font-family: 'Roboto', 'Roboto-Fallback', sans-serif;
}

When the page loads, the browser instantly paints Roboto-Fallback. It's just local Arial, but horizontally stretched and vertically squished to match the exact mathematical footprint of Roboto. When the real Roboto file finishes downloading, the swap occurs, the letterforms change design, but the layout doesn't move a single pixel.

Real-world stories

The "Cancel" Button Trap

A major e-commerce platform was struggling with cart abandonment and furious customer support tickets. Users were complaining that they were accidentally emptying their carts. The engineering team recorded user sessions and found the culprit. The site loaded a heavy branded font. A user would see the "Checkout" button right next to the "Empty Cart" button. Just as their mouse clicked down on "Checkout," the branded font loaded. The branded font was significantly wider than the system default. The text reflowed, pushing the "Checkout" button to the right, and positioning the "Empty Cart" button exactly under the user's descending mouse click. Implementing size-adjust and metric overrides locked the buttons in place during the font swap, immediately dropping the accidental cancellation rate.

The News Article Earthquake

A popular tech blog underwent a massive redesign, complete with a beautiful, modern serif font for long-form reading. To prioritize speed, they used font-display: swap. However, readers with slower connections experienced a jarring phenomenon: they would load an article, scroll down three paragraphs, and start reading. Two seconds later, the serif font would load. Because the serif font had a larger ascent and descent than the system default, every single line of text grew by a fraction of a millimeter. Over the course of three paragraphs, this compounded into a massive vertical shift, throwing the sentence the user was reading entirely off the screen. By adding an ascent-override and descent-override to a local fallback font, the text swapped perfectly in place, keeping the reader's eye exactly where it belonged.

The Lighthouse Score Heartbreak

A frontend developer spent three weeks optimizing a web application for Google's Core Web Vitals. They compressed images into WebP, deferred non-critical JavaScript, and minified their CSS. They ran Google Lighthouse and confidently expected a perfect 100 score. Instead, they got a 75. The report flagged a massive Cumulative Layout Shift penalty. The entire shift was caused by a single <H1> headline wrapping from one line to two lines when a custom web font loaded. Instead of redesigning the header or relying on invisible text, the developer generated exact fallback metrics for the heading font. The layout shift dropped to zero, the Lighthouse score hit 100, and the developer finally got to sleep.

Common mistakes and traps

  • Guessing the percentages: You cannot eyeball these numbers. The calculations require extracting the exact Unique Units Per Em (UPM), ascent, descent, and line gap data directly from the binary tables inside both the web font and the system font files, and cross-multiplying them. Always use a programmatic generator to get the math right.
  • Applying overrides to the web font: A classic trap is adding size-adjust directly to the @font-face declaration of the custom web font. This achieves nothing. The overrides must be applied to a completely separate @font-face declaration that loads a local() system font, which you then place second in your font-family stack.
  • Picking structurally opposite fallbacks: Font metrics can only do so much. If your custom font is an ultra-condensed sans-serif, and you use a wide, sweeping serif font as your local fallback base, the size-adjust math will make the serif font look completely distorted and illegible while waiting to swap. Always pick a local system font that has a roughly similar skeletal structure to your web font.
  • Forgetting about line-gap-override: Developers often focus entirely on width and height and assume setting the line gap to normal or omitting it is fine. If the browser decides to add default leading to the fallback font that doesn't perfectly match the web font's internal leading, your vertical rhythm will shift. Always explicitly define it.

Why it belongs on your radar

You should think about font fallback metrics the moment you decide to use a custom web font on any project where user experience and SEO matter.

Historically, typography on the web was a compromise between aesthetics (using nice fonts) and performance (avoiding jumping layouts). With the advent of CSS font metrics, that compromise is dead. Whenever you are auditing a site's Core Web Vitals, struggling with a high CLS score, or building reusable UI components that cannot afford to jitter on the screen, font fallback overrides are the definitive engineering solution. It transforms web fonts from a loading liability into a seamless, imperceptible transition.

Go deeper

Theory done. Time to get your hands dirty — 100% in your browser.

Try the tool: Font Fallback Generator (CLS)