Font Swapping Without Layout Shift

Web fonts present a classic trade-off: system fonts render instantly but lack brand character, while custom fonts take time to load. The usual workaround—render content with a fallback font first, then swap in the web font—introduces its own problem. When the real font loads, it often has different metrics than the fallback, causing visible jumps in layout and potentially triggering cumulative layout shift (CLS).

The CSS @font-face descriptor size-adjust addresses this directly. Available since Chrome 92, Edge 92, Firefox 92, and Safari 17, it lets authors scale a font's glyphs at the declaration level, normalizing visual size across different typefaces. A headline set at 64px will stay at 64px visually, regardless of which font family is rendering it.

Adam Argyle

How size-adjust Works

The syntax is straightforward:

@font-face {
  font-family: "Adjusted Typeface";
  size-adjust: 150%;
  src: url(some/path/to/typeface.woff2) format('woff2');
}

In this example:

  1. A custom face named Adjusted Typeface is registered.
  2. size-adjust scales each glyph to 150% of its default size.
  3. The adjustment applies only to that one imported face.

Once registered, the face is used like any other:

h1 {
  font-family: "Adjusted Typeface";
}

Preventing Swap Shifts

Set a consistent font-size across your document, and a face like "Press Start 2P" might render larger than expected due to its internal metrics. The effect is clear in this interactive demo: switching typefaces produces height differences, jarring content shifts, and even repositioning of interactive elements like dropdowns.

The fix for a seamless font swap is to calibrate your fallback font against the web font you plan to load. By setting size-adjust in the fallback's @font-face rule, both faces present at the same visual scale, so swapping causes minimal visual change. Calibration can be done by trial and error, or with a purpose-built calculator, such as Malte Ubl's size-adjust tool that generates fallback CSS for a chosen Google Font.

As an example, aligning a local font like Arial to a downloaded custom typeface requires an @font-face declaration that registers Arial under a new name with the correct size-adjust value:

@font-face {
  font-family: 'Adjusted Arial';
  size-adjust: 86%;
  src: local(Arial);
}

@font-face {
  font-family: 'Cookie';
  size-adjust: 90.25%;
  src: url(...woff2) format('woff2');
}

Two Calibration Strategies

Authors must decide which font is the calibration target. This choice shapes how adjustments are applied:

  1. Remote target: Local fonts are adjusted to match a downloaded brand font. This lets designers and developers use the same reference typography across the design system, with the brand font as the fixed reference point.
  2. Local target: Downloaded fonts are adjusted to match a local system font. This ensures the page renders perfectly with zero adjustments before any custom font arrives.

For the remote-target approach, the fallback face can be tuned across platforms using a comma-separated list of local font names:

@font-face {
    font-family: "Roboto-fallback-1";
    size-adjust: 100.06%;
    src: local("Arial");
}

@font-face {
    font-family: "Roboto-fallback-2";
    size-adjust: 99.94%;
    src: local("Arimo");
}

@font-face {
  font-family: "Roboto Regular";
  src: url(some/path/to/roboto.woff2) format('woff2');
}

html {
  font-family: "Roboto Regular", "Roboto-fallback-1", "Roboto-fallback-2";
}

The local-target strategy is used when the layout must hinge on an unadjusted fallback, such as Arial-based metrics:

@font-face {
  font-family: "Rad Brand - Adjusted For Arial";
  src: url(some/path/to/a.woff2) format('woff2');
  size-adjust: 110%;
}

html {
  font-family: "Rad Brand - Adjusted For Arial", Arial;
}

Finer Metrics Overrides

Generic uniform scaling via size-adjust is useful, but insufficient when vertical metrics need independent control. Three additional descriptors work alongside it: ascent-override, descent-override, and line-gap-override. These are implemented in Chromium 87+ and Firefox 89+.

ascent-override

Defines the metric above the baseline. Adjusting it upward tightens the space above capital letters, moving them closer to the overall bounding box:

@font-face {
  font-family: "Ascent Adjusted Arial Bold";
  src: local(Arial Bold);
  ascent-override: 71%;
}

descent-override

Defines the space below the baseline. Increasing it provides more gap beneath letters like D and O:

@font-face {
  font-family: "Ascent Adjusted Arial Bold";
  src: local(Arial Bold);
  descent-override: 0%;
}

line-gap-override

Sets the external leading recommended by the font itself. A value of 50% adds vertical spacing above and below the text run:

@font-face {
  font-family: "Line Gap Adjusted Arial";
  src: local(Arial);
  line-gap-override: 50%;
}

Combining Explict Size with Precise Metrics

Authors can nest these descriptors within a single @font-face rule to trim the default text bounding box further:

scissors above and blow the word overrides, demonstrating the areas the
features can trim to

Used together, size-adjust and the three metric overrides give fine-grained control over how text is presented within page layout. This reduces the degree of layout shift during font swaps, making content more predictable before custom fonts finish loading. For deeper reading, the latest specs and compatibility notes are available on MDN and the CSS Fonts Level 5 specification.