All cases
RL-013 / Layout ShiftEvidence verified

Measure and Reduce Layout Shift From Web Font Substitution

A deterministic system-font model isolates the geometry change caused by swapping fonts with different metrics, then compares it with making the final face available before the first measured layout.

Experiment mode

Fixture viewport

Failingfallback -> final
Viewport height
--
Text before / after
--
Target movement
--
Fixedfinal before layout
Viewport height
--
Text before / after
--
Target movement
--

Three-browser evidence verified. Live values come from your current browser. The formal matrix records the controlled browser runs and fixture revision used for the published conclusion.

Observed failure

The final font changes the heading's line count

The failing fixture begins with Courier New and then applies Georgia to a fixed project-authored heading. At narrow widths, the final face occupies fewer lines and moves the following status panel upward.

Minimal code diff

Stabilize font metrics before the first layout

The fixed fixture begins with the final face already available, so the same mutation does not change line wrapping. In production, load critical fonts early or metric-match the fallback with accurate font-face overrides.

Before

font-family: 'Courier New', monospace;
/* later swaps to Georgia */

After

font-family: Georgia, serif;
/* final face available before layout */

Root cause

Fallback and final fonts can have different glyph widths, ascent, descent, and line-gap metrics. A heading that fits in three lines with one face may use four with another. When the browser swaps faces after initial layout, everything after that heading can move.

This fixture deliberately isolates metric substitution from networking. The failing state changes from Courier New to Georgia after the first measurement. The fixed state starts and ends with Georgia, representing a critical final face available before first layout. It is a repeatable geometry test, not a simulation of a specific connection or font provider.

Production choices

Early loading is one option, but it is not the only one. For a real web font, measure the actual final and fallback files. Depending on the result, use an appropriate preload and delivery strategy, choose a closer fallback, or define tested size-adjust, ascent-override, descent-override, and line-gap-override values. Do not copy percentages from an unrelated font pair.

Measurement support

Every browser run records the heading height before and after substitution plus the following panel’s vertical movement. Chromium additionally records non-input layout-shift performance entries. Firefox and WebKit results do not claim Layout Instability API support.

When this fix is not enough

Making every font render-blocking can delay text and harm other performance metrics. Metric overrides can also be wrong for a different weight, script, variable-font axis, or platform font version. Test the exact production files, text languages, weights, and browser set.

Regression condition

At 320 and 375 CSS pixels, the failing substitution must change heading height and move the following panel by a positive amount. The fixed state must leave both the heading height and target position unchanged. Both states must have zero page-level horizontal overflow.

Primary references

Sources checked for this case

  1. CSS Fonts Module Level 5 - font metric overrides - W3C, checked 2026-08-04
  2. Best practices for fonts - Google web.dev, checked 2026-08-04
  3. Optimize Cumulative Layout Shift - web fonts - Google web.dev, checked 2026-08-06

Formal evidence

Browser and viewport matrix

Three-browser run

Cells show failing / fixed target movement in CSS pixels.

Browser32037576810241440
Chromium+29 / +0+29 / +0+0 / +0+0 / +0+0 / +0
Firefox+29 / +0+29 / +0+0 / +0+0 / +0+0 / +0
WebKit+29 / +0+29 / +0+0 / +0+0 / +0+0 / +0
20/20 recorded assertions passed
  • chromium-failing-movement-32029px
  • chromium-fixed-movement-3200px
  • chromium-horizontal-reflow-3200px / 0px
  • chromium-layout-instability-api-3200.005858153343200684 / 0
  • chromium-failing-movement-37529px
  • chromium-fixed-movement-3750px
  • chromium-horizontal-reflow-3750px / 0px
  • chromium-layout-instability-api-3750.004772667261445473 / 0
  • firefox-failing-movement-32029px
  • firefox-fixed-movement-3200px
  • firefox-horizontal-reflow-3200px / 0px
  • firefox-failing-movement-37529px
  • firefox-fixed-movement-3750px
  • firefox-horizontal-reflow-3750px / 0px
  • webkit-failing-movement-32029px
  • webkit-fixed-movement-3200px
  • webkit-horizontal-reflow-3200px / 0px
  • webkit-failing-movement-37529px
  • webkit-fixed-movement-3750px
  • webkit-horizontal-reflow-3750px / 0px