Every web font has a loading window — the gap between the page rendering and the font file arriving. During this window, the browser uses your fallback font. If the fallback is visually different (different size, different line height, different character width), text reflows when the web font swaps in. That reflow is Cumulative Layout Shift (CLS), and it degrades both user experience and Core Web Vitals scores.
The goal isn't just "pick a fallback." It's engineering a fallback that occupies the same physical space as the web font, so the swap is seamless. This guide covers the system font stack, metric matching with CSS overrides, and the edge cases that trip up most implementations — monospace, emoji, CJK, and Unicode coverage gaps.
The Modern System Font Stack
System fonts are pre-installed on the user's device and render instantly — zero network requests, zero loading delay. Every fallback chain should end with system fonts.
Sans-Serif Stack
font-family: 'Your Web Font', system-ui, -apple-system, 'Segoe UI',
Roboto, 'Noto Sans', Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;| Value | Platform | Font |
|---|---|---|
system-ui | macOS/iOS | San Francisco (SF Pro) |
system-ui | Windows | Segoe UI |
system-ui | Android | Roboto |
-apple-system | Older Safari | San Francisco (pre-system-ui support) |
'Segoe UI' | Windows | Direct fallback for older browsers |
Roboto | Android/ChromeOS | Direct fallback |
'Noto Sans' | Linux | Common pre-installed font |
Ubuntu | Ubuntu Linux | Default UI font |
Cantarell | GNOME/Fedora | Default UI font |
'Helvetica Neue' | macOS (older) | Pre-San Francisco default |
sans-serif | Any | Generic fallback |
system-ui resolves to the platform's default UI font, covering macOS, Windows, and Android in one keyword. The named fonts after it are fallbacks for browsers that don't support system-ui (increasingly rare in 2026) and for Linux, which has fragmented font installation.
Serif Stack
font-family: 'Your Serif Font', 'Iowan Old Style', 'Palatino Linotype',
Palatino, Georgia, 'Noto Serif', serif;Serif system fonts are less standardized across platforms. Georgia is the universal safe bet — installed on macOS, Windows, and most Linux distributions. Palatino/Palatino Linotype provides a more elegant fallback. 'Iowan Old Style' is available on Apple devices and is one of the best-looking pre-installed serif fonts.
Monospace Stack
font-family: 'Your Mono Font', ui-monospace, 'SF Mono', 'Cascadia Code',
'Consolas', 'Liberation Mono', 'DejaVu Sans Mono', Menlo, Monaco, monospace;ui-monospace resolves to the platform's default monospace UI font (SF Mono on macOS, Cascadia Code on modern Windows). The named fonts cover specific platforms: Consolas for Windows, Menlo/Monaco for older macOS, Liberation Mono and DejaVu Sans Mono for Linux.
Monospace fallbacks are critical for code blocks and preformatted text. If the fallback isn't monospace, code alignment breaks completely — columns don't line up, indentation is inconsistent, and ASCII art becomes unreadable.
Metric Matching: Zero-CLS Font Swaps
The biggest font performance problem in 2026 isn't load time — it's the layout shift when the web font replaces the fallback. Even with font-display: swap and preloading, the swap causes CLS if the two fonts have different metrics.
Why Font Metrics Cause Layout Shift
Two fonts with the same font-size: 16px produce different physical dimensions because fonts define their own internal metrics:
- x-height: The height of lowercase letters (like 'x'). A taller x-height makes text appear larger
- Ascender height: How far tall letters (like 'd', 'h') extend above the x-height
- Descender depth: How far letters like 'g', 'p', 'y' extend below the baseline
- Character width (advance width): How wide each character is, determining how many characters fit per line
- Line gap: Extra space the font requests between lines, on top of ascender + descender
When Inter (x-height: 0.52 of font-size) replaces Arial (x-height: 0.52 of font-size), the swap is almost invisible because their metrics are close. When a display font with narrow characters replaces Arial, every line break changes and paragraphs gain or lose lines — visible, jarring CLS.
CSS Font Metric Override Properties
Four CSS properties let you adjust the fallback font's metrics to match the web font:
/* Define a custom fallback with adjusted metrics */
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107.64%;
ascent-override: 89.71%;
descent-override: 22.43%;
line-gap-override: 0%;
}
/* Use both in the font stack */
body {
font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;
}What each property does:
size-adjust— Scales the fallback font's glyphs by a percentage. If the fallback has narrower characters than the web font, increase this value so they occupy the same widthascent-override— Sets the ascender height as a percentage of font-size. Controls where the top of the text box sitsdescent-override— Sets the descender depth. Controls where the bottom of the text box sitsline-gap-override— Sets extra space between lines. Most web fonts use 0; set fallback to 0 to match
When all four values are tuned correctly, the fallback font occupies the exact same space as the web font. Lines break at the same points, paragraphs have the same height, and the swap produces zero layout shift.
Calculating Override Values
Three approaches, from easiest to most precise:
- Fontaine (npm): Generates @font-face CSS with metric overrides automatically.
npx fontainescans your font files and outputs the CSS - Next.js next/font: Calculates overrides automatically when you use
next/font/googleornext/font/local. Zero configuration - Manual calculation: Read the OS/2 table from both fonts (fonttools or opentype.js), then calculate:
size-adjust = web_avg_char_width / fallback_avg_char_width * 100% ascent-override = web_ascent / (web_ascent + web_descent + web_line_gap) * 100% descent-override = web_descent / (web_ascent + web_descent + web_line_gap) * 100% line-gap-override = web_line_gap / (web_ascent + web_descent + web_line_gap) * 100%
Override Values for Popular Fonts
| Web Font | Fallback | size-adjust | ascent-override | descent-override | line-gap-override |
|---|---|---|---|---|---|
| Inter | Arial | 107.64% | 89.71% | 22.43% | 0% |
| Roboto | Arial | 100.3% | 92.49% | 24.34% | 0% |
| Open Sans | Arial | 105.27% | 101.18% | 27.25% | 0% |
| Lato | Arial | 97.38% | 102.78% | 22.22% | 0% |
| Montserrat | Arial | 103.6% | 84.05% | 20.69% | 0% |
| Source Code Pro | Consolas | 92.8% | 98.37% | 27.4% | 0% |
These values produce near-zero CLS for the most common Google Fonts when Arial (sans-serif) or Consolas (monospace) is the system fallback. Adjustments may be needed for different fallback fonts.
X-Height Matching
If CSS metric overrides aren't an option (older browser support, email templates), choose a fallback font whose x-height is closest to your web font's x-height. X-height is the single most important metric for visual similarity — it determines how "big" text appears at a given font-size.
General matching guidelines:
- Inter, Roboto, Open Sans: Arial is a close x-height match. All have high x-heights (~0.52 ratio)
- Montserrat, Poppins: Verdana is a closer match than Arial (wider characters, similar x-height)
- Georgia: Times New Roman is the closest pre-installed serif match
- Lato: Helvetica Neue on macOS, Arial on Windows
The ex CSS unit is the x-height of the current font. You can use it for debugging: set a 1ex-tall box in both the web font and fallback font and compare visually.
Emoji Fallbacks
Emoji rendering uses platform-specific color fonts that are separate from your text font. The fallback chain for emoji is handled by the operating system, not your CSS font-family declaration. But there are cases where you want explicit control.
Emoji Font Stack
/* Explicit emoji fallback (rarely needed) */
.emoji {
font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji',
'Twemoji Mozilla', sans-serif;
}| Font | Platform | Style |
|---|---|---|
| Apple Color Emoji | macOS, iOS | Apple's emoji design |
| Segoe UI Emoji | Windows | Microsoft's emoji design |
| Noto Color Emoji | Android, Linux | Google's emoji design |
| Twemoji Mozilla | Firefox (bundled) | Twitter's emoji design |
Most of the time, you don't need an explicit emoji font stack. Browsers automatically fall back to the platform's emoji font when they encounter emoji characters. The explicit stack is useful for ensuring consistent emoji rendering in specific UI elements or when using emoji as icons.
Text vs Emoji Presentation
Some Unicode characters have both text and emoji presentations. The "information" symbol (i), the "warning" triangle, and various arrows exist as both plain text glyphs and color emoji. The Variation Selector controls which version renders:
U+FE0E(VS15) forces text presentation — monochrome glyph from the text fontU+FE0F(VS16) forces emoji presentation — color glyph from the emoji font
If your text font renders certain symbols as emoji unexpectedly, append VS15 to force the text version. Conversely, if emoji render as text (common on some Linux configurations), append VS16.
Unicode Coverage Gaps
Every font covers a specific set of Unicode code points. When text contains characters outside the font's coverage, the browser falls back to the next font in the stack. If no font in the stack covers the character, the user sees a tofu — the empty rectangle (□) that indicates a missing glyph.
Preventing Tofu
- Include Noto Sans in your fallback chain: Google's Noto font family ("No Tofu") aims to cover every Unicode character.
'Noto Sans'is pre-installed on Android and many Linux distributions, and covers Latin, Greek, Cyrillic, Arabic, Hebrew, Devanagari, and more - Use unicode-range @font-face: Declare separate @font-face rules for different scripts, each pointing to a font that covers that range. The browser loads only the fonts needed for the characters on the page
- Test with diverse content: If your site accepts user-generated content, test with Arabic, CJK, and emoji characters to verify fallback rendering
CJK Fallback Chain
/* For sites that may display CJK characters */
font-family: 'Your Font', system-ui, -apple-system, 'Segoe UI',
Roboto, 'Noto Sans', 'Noto Sans CJK SC', 'Noto Sans CJK JP',
'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans', sans-serif;'Noto Sans CJK SC' covers Simplified Chinese, 'Noto Sans CJK JP' covers Japanese, 'Microsoft YaHei' is the default CJK font on Windows, and 'PingFang SC' and 'Hiragino Sans' cover macOS/iOS. Including all of these ensures CJK characters render correctly regardless of platform.
A bulletproof fallback strategy has three layers: (1) a system font stack that covers every platform, (2) CSS metric overrides that match the fallback's metrics to the web font, and (3) extended Unicode coverage for edge-case characters. The metric matching layer is the most impactful — it turns a jarring font swap into an almost invisible transition.
Pair this with font-display: swap, preloading, and WOFF2 compression (convert your fonts to WOFF2) for a complete web font strategy. Users see text instantly in a visually similar fallback, the web font loads in the background, and the swap happens with zero layout shift.