Build zero-download font stacks using system UI fonts and web-safe fallbacks that render instantly on every platform while maintaining visual quality.
Not every page needs a custom web font. System font stacks deliver instant rendering — zero network request, zero FOIT, zero CLS — and on modern operating systems they look excellent. Even when you do load a custom font, the fallback stack determines what users see during loading. A poorly crafted fallback causes large layout shifts; a well-crafted one is nearly invisible. This lesson teaches you to build fallback stacks intentionally, not cargo-cult them.
Every modern OS ships a high-quality system font optimised for screen readability. macOS and iOS use San Francisco (SF Pro / SF Compact) — a humanist sans with dynamic tracking adjustments at small sizes. Windows uses Segoe UI — a humanist sans with excellent ClearType rendering. Android uses Roboto — a geometric humanist sans. Linux typically uses Ubuntu, Cantarell, or DejaVu Sans. Chrome OS uses Roboto. These are all professional-grade typefaces designed specifically for UI use. The -apple-system keyword resolves to San Francisco on Apple platforms; BlinkMacSystemFont is a Chromium-specific alias for the same font. system-ui is the modern standard CSS keyword that resolves to the platform UI font on all supporting browsers (Chrome 56+, Firefox 92+, Safari 11+). It replaces the need for platform-specific prefixed keywords on new projects.
1/* Modern system font stack using the standard keyword */
2body {
3 font-family: system-ui, sans-serif;
4}
5
6/* Comprehensive stack for maximum compatibility */
7body {
8 font-family:
9 system-ui, /* Standard: resolves on most browsers */
10 -apple-system, /* Safari <11 on macOS/iOS */
11 BlinkMacSystemFont, /* Chrome on macOS */
12 'Segoe UI', /* Windows */
13 Roboto, /* Android, Chrome OS */
14 Oxygen, /* KDE Linux */
15 Ubuntu, /* Ubuntu Linux */
16 Cantarell, /* GNOME Linux */
17 sans-serif; /* Generic fallback */
18}Web-safe fonts are typefaces so widely installed they can be used without @font-face declarations. They include: Arial (Windows/Mac/Linux, Grotesque sans), Helvetica (macOS, same metrics as Arial), Times New Roman (ubiquitous, Transitional serif), Georgia (screen-optimised serif, excellent x-height), Verdana (screen-optimised sans, wide letterforms, excellent at small sizes), Trebuchet MS (Humanist sans), Impact (display only — extreme weight), Courier New (monospace), Comic Sans MS (avoid for professional use), Palatino / Book Antiqua (Old Style serif), Garamond (on Windows only — requires fallback), Tahoma (Windows, compact sans). Of these, Georgia and Verdana were specifically designed for screen legibility by Matthew Carter in 1996 and remain excellent choices for body text when you want zero download overhead. Georgia at 16px body text with 1.6 line-height is highly readable and requires zero network requests.
1/* Editorial body text: Georgia is purpose-built for screens */
2article {
3 font-family: Georgia, 'Times New Roman', Times, serif;
4 font-size: 1.125rem; /* 18px */
5 line-height: 1.7;
6 max-width: 65ch;
7}
8
9/* UI/dashboard: Verdana excellent at small sizes */
10.ui-label {
11 font-family: Verdana, Geneva, Tahoma, sans-serif;
12 font-size: 0.8125rem; /* 13px */
13 line-height: 1.4;
14}A font-family declaration is a priority list — the browser tries each entry and uses the first one it finds installed. Good stacks follow these ordering rules: (1) your preferred custom font first, (2) platform-specific system fonts that closely match your preferred font's metrics and style, (3) widely-available web-safe fonts, (4) a generic family keyword last. The goal of entries 2-3 is to minimise visual difference between the loaded custom font and the fallback so users perceive as little shift as possible. If your custom font is a Humanist sans, your fallbacks should also be Humanist sans — not Helvetica (Grotesque) or Georgia (serif). Match not just category but proportions: Inter and Calibri share similar proportions; Fira Sans and Gill Sans are closer in character width than Inter and Segoe UI.
1/* Stack for Inter (Humanist Sans): closely matched fallbacks */
2body {
3 font-family:
4 'Inter', /* Custom — loaded via @font-face */
5 'Segoe UI', /* Windows: close proportions to Inter */
6 system-ui, /* Modern platforms: use system UI font */
7 -apple-system, /* Apple: San Francisco */
8 sans-serif; /* Generic */
9}
10
11/* Stack for Lora (Transitional Serif): matched fallbacks */
12article {
13 font-family:
14 'Lora', /* Custom serif */
15 Georgia, /* Web-safe transitional serif */
16 'Times New Roman',/* Final web-safe serif fallback */
17 serif; /* Generic */
18}Layout shift from font swaps happens because the custom font and the fallback have different x-heights, causing different line heights and element sizes. The font-size-adjust property normalises x-heights by scaling the rendered font to match a target aspect ratio (x-height / font-size). To calculate: measure the x-height and font-size of your custom font (use browser devtools or a character inspector), divide x-height by font-size. Set font-size-adjust to this value on the element. When the custom font swaps in, the visual size difference is minimised because the fallback has been scaled to match the custom font's x-height. Browser support: Chrome 127+ (stable as of 2024), Firefox since v92, Safari TP. Provide it progressively — it enhances but is not required.
1/* Inter has an aspect value of approximately 0.527 */
2/* Without font-size-adjust: Arial renders much smaller than Inter */
3body {
4 font-family: 'Inter', Arial, sans-serif;
5 /* Scale Arial's x-height to match Inter's */
6 font-size-adjust: 0.527;
7 /* Now when Inter loads, minimal visual size shift occurs */
8}
9
10/* How to measure: temporarily set only the fallback and */
11/* adjust until lowercase letters are the same visual height */
12.measure {
13 font-family: Arial, sans-serif; /* Fallback only */
14 font-size-adjust: 0.527; /* Tune until x-height matches Inter */
15 font-size: 1rem;
16}System fonts win when: the application is a complex UI (dashboards, dev tools, admin panels) where brand expression is secondary to density and performance; the audience is on metered connections; the target is near-instant Lighthouse performance scores. Custom fonts win when: brand identity is central (marketing, landing pages, editorial); the typeface provides measurably better legibility for specific content types (e.g., a monospace with ligatures for a code-education product); the design system requires consistency across platforms that system fonts cannot guarantee. A pragmatic approach: use system-ui for app UI text (inputs, labels, navigation), and load a single custom font for headings/display text using font-display: optional with a preload hint. This gives you brand expression in the most visible elements with zero performance penalty for body text.
1/* Pragmatic hybrid: system font for body, custom for display */
2:root {
3 /* Body text: system font — instant, zero layout shift */
4 --font-body: system-ui, -apple-system, sans-serif;
5
6 /* Display: custom font — brand expression at headlines */
7 --font-display: 'Fraunces', Georgia, serif;
8}
9
10body {
11 font-family: var(--font-body);
12}
13
14h1, h2, .display {
15 font-family: var(--font-display);
16 /* font-display: optional in @font-face prevents late swap */
17}Build a complete font stack that works across all platforms with minimal layout shift.
Next lesson covers Google Fonts optimisation — self-hosting strategies, subsetting with fonttools, and the performance trade-offs of using Google's CDN versus serving fonts yourself.
Your progress is saved in this browser, on this device only.