CSS字体栈:备用字体最佳实践
CSS Font Stacks: Best Practices for Fallback Fonts
A CSS font stack is the ordered list of typefaces specified in a font-family declaration. The browser works through the list from left to right, using the first font it can find and render. If none of the named fonts are available, it falls back to the final value — always a generic family name like sans-serif, serif, or monospace.
Writing good font stacks is more than listing a few font names. It's about understanding platform differences, how fallback fonts affect layout, how to use modern CSS properties to match fallback metrics, and which recipes to reach for in common design scenarios.
How CSS Font Stacks Work
When a browser encounters font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif, it checks:
- Is 'Inter' available? (Downloaded via
@font-face, or pre-installed) — Use it - If not: Is 'Helvetica Neue' installed? — Use it
- If not: Is 'Arial' installed? — Use it
- If not: Use the OS default sans-serif font
The browser evaluates this per character, not per element. If a character in your text doesn't exist in Inter's glyph set (say, a Chinese character), the browser falls back to the next font in the stack that contains that glyph. This makes font stacks a partial per-character fallback system as well.
What "Available" Means
A font is available if:
- It was declared with @font-face and the file has been downloaded
- It was referenced via local() in an @font-face and is installed on the system
- It is a built-in system font
/* This declares Inter as available via download */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
}
/* Now this stack uses the downloaded Inter */
body {
font-family: 'Inter', system-ui, sans-serif;
}
Generic Families
Always end your stack with a generic family. Never leave a font-family declaration without one.
/* Generic families */
.sans { font-family: system-ui, sans-serif; }
.serif { font-family: Georgia, serif; }
.mono { font-family: 'Courier New', monospace; }
.cursive { font-family: cursive; }
.fantasy { font-family: fantasy; }
/* The newest: system-ui, ui-sans-serif, ui-serif, ui-monospace */
.native-ui { font-family: system-ui, -apple-system, sans-serif; }
The System Font Stack (system-ui)
The system font stack tells the browser to use the operating system's default UI font. This produces text that feels native to the user's platform — the same font they see in system menus, dialogs, and native apps.
The Minimal System Stack
body {
font-family: system-ui, -apple-system, sans-serif;
}
system-ui is the CSS specification keyword for the system UI font. Browser support is excellent across all modern platforms. -apple-system is a legacy alias for older Safari. The final sans-serif is the generic fallback.
What This Resolves To
| Platform | Font |
|---|---|
| macOS / iOS | SF Pro / San Francisco |
| Windows 11+ | Segoe UI Variable |
| Windows 10 | Segoe UI |
| Android | Roboto |
| ChromeOS | Roboto |
| Linux | Depends on distribution — often Cantarell, Ubuntu, or Noto Sans |
The Extended System Stack
Some teams use a longer stack for maximum coverage of older systems:
body {
font-family:
system-ui,
-apple-system, /* Safari on macOS/iOS < 2019 */
BlinkMacSystemFont, /* Chrome on macOS < 2019 */
'Segoe UI', /* Windows */
Roboto, /* Android */
Oxygen, /* KDE Linux */
Ubuntu, /* Ubuntu Linux */
Cantarell, /* GNOME Linux */
'Helvetica Neue', /* macOS/iOS fallback */
Arial, /* Universal fallback */
sans-serif; /* Generic */
}
This extended stack is what GitHub famously used before simplifying to system-ui. For most modern projects, the shorter version is sufficient.
When to Use the System Stack
Use system-ui when:
- Building developer tools, dashboards, or admin interfaces
- Performance is the top priority and custom fonts aren't worth the overhead
- You want a native app aesthetic
- The UI is a means to an end, not a brand expression
Use a custom font stack when: - Brand typography is important - The reading experience requires a specific typeface (long-form content) - You need consistent cross-platform appearance
Building Custom Stacks: Web Font + Fallbacks
When you use a custom web font, you need fallbacks that activate while the font loads and for users where the font fails to load.
Matching Fallback to the Web Font
The ideal fallback resembles your web font in category, proportion, and weight. A good fallback minimizes the visual jump when the web font swaps in.
/* Inter → system-ui → Helvetica Neue → Arial */
body {
font-family: 'Inter', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
}
/* Source Serif 4 → Georgia → Times New Roman */
.article-body {
font-family: 'Source Serif 4', Georgia, 'Times New Roman', serif;
}
/* Lora → Palatino (wider, old-style serif) */
blockquote {
font-family: 'Lora', Palatino, 'Book Antiqua', Georgia, serif;
}
/* Playfair Display → Didot (similar high-contrast display serif) */
.display-heading {
font-family: 'Playfair Display', Didot, 'Palatino Linotype', serif;
}
/* JetBrains Mono → Menlo → Consolas → monospace */
code {
font-family: 'JetBrains Mono', Menlo, Consolas, 'Courier New', monospace;
}
Platform-Aware Fallback Chaining
Different platforms have different pre-installed fonts. A well-crafted stack handles each:
/* Heading stack: Playfair Display → platform serif alternates */
h1, h2, h3 {
font-family:
'Playfair Display', /* Web font */
'Palatino Linotype', /* Windows */
Palatino, /* macOS */
'Book Antiqua', /* Older Windows */
Georgia, /* Universal serif fallback */
serif;
}
/* Sans-serif body: Roboto → platform sans */
body {
font-family:
'Roboto', /* Web font */
'Helvetica Neue', /* macOS + iOS older */
Helvetica, /* macOS + legacy */
Arial, /* Windows + universal */
sans-serif;
}
size-adjust: Matching Fallback Metrics
The size-adjust descriptor in @font-face is a game-changer for reducing layout shift when fonts swap. It lets you scale a fallback font to approximately match the dimensions of the web font.
When fonts have different x-heights, cap heights, and character widths, swapping from fallback to web font causes content to reflow — paragraphs shift, buttons resize, headings reflux. size-adjust lets you compensate by scaling the fallback.
How size-adjust Works
/* Create a modified version of Arial that matches Inter's metrics */
@font-face {
font-family: 'Inter Fallback';
src: local('Arial'); /* Use Arial from the system */
size-adjust: 107%; /* Scale up to match Inter's width */
ascent-override: 90%; /* Match Inter's ascent */
descent-override: 22%; /* Match Inter's descent */
line-gap-override: 0%; /* Match Inter's line gap */
}
/* Web font */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
}
/* Stack: Inter → adjusted Arial fallback → system sans */
body {
font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;
}
When the page first loads, Inter Fallback (the adjusted Arial) renders. When Inter finishes loading and swaps in, the layout change is minimal because the metrics have been compensated.
Finding the Right Values
The exact values depend on the specific fonts being compared. You can find them by:
- Computed experimentally: Render the same text in both fonts at the same size and measure the difference
- Using a tool: The Font Style Matcher and Malte Ubl's Automatic Font Matching calculate these values
/* Roboto → Roboto fallback using adjusted Arial */
@font-face {
font-family: 'Roboto Fallback';
src: local('Arial');
size-adjust: 100%;
ascent-override: 92.7189%;
descent-override: 24.4099%;
line-gap-override: 0%;
}
/* Lora → Lora fallback using adjusted Georgia */
@font-face {
font-family: 'Lora Fallback';
src: local('Georgia');
size-adjust: 104%;
ascent-override: 87%;
descent-override: 23%;
line-gap-override: 0%;
}
The Resulting Font Stacks
body {
font-family: 'Roboto', 'Roboto Fallback', sans-serif;
}
.serif-body {
font-family: 'Lora', 'Lora Fallback', serif;
}
This approach produces near-zero Cumulative Layout Shift (CLS) from font swapping — a Core Web Vital metric that Google uses for search ranking. If your CLS is impacted by font loading, size-adjust is your most effective tool.
Platform-Specific Considerations
macOS and iOS
macOS ships with a small set of pre-installed fonts: - Sans-serif: Helvetica Neue, Helvetica, Arial, system-ui (San Francisco) - Serif: Georgia, Times New Roman, Palatino - Mono: Menlo, Courier New - Notable extras: Didot, Optima, Gill Sans, Futura (on macOS)
macOS has long had subpixel antialiasing for fonts. Starting with macOS Mojave, Apple disabled it across the OS. -webkit-font-smoothing: antialiased can still affect rendering — see the font smoothing guide for details.
Windows
Windows 11 ships with a richer set of fonts than previous versions: - Sans-serif: Segoe UI Variable, Arial, Calibri, Tahoma, Verdana, Trebuchet MS - Serif: Georgia, Times New Roman, Cambria, Palatino Linotype - Mono: Consolas, Courier New, Cascadia Code (in newer builds)
Verdana and Georgia were designed specifically for screen readability and have unusually wide character widths — keep this in mind when using them as fallbacks.
Android
Android ships with Roboto as its primary system font across the standard interface. Noto (for international character coverage) is also bundled. Most Android browsers ship their own font fallback behavior.
/* A robust cross-platform body stack */
body {
font-family:
'Inter',
'Inter Fallback', /* Adjusted fallback */
system-ui,
-apple-system,
'Segoe UI',
Roboto,
Arial,
sans-serif;
}
Common Font Stack Recipes
Recipe 1: Clean Modern Sans (Tech / Startup)
:root {
--font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
Inter or system UI fallback. Clean, readable, neutral. Works at every size.
Recipe 2: Humanist Sans (Agency / Creative)
:root {
--font-sans: 'Source Sans 3', 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
}
Warm humanist feel. Gill Sans as a fallback (widely installed on macOS).
Recipe 3: Geometric Display (Brand Headings)
:root {
--font-display: 'Raleway', 'Futura', 'Century Gothic', 'Trebuchet MS', sans-serif;
}
Futura is pre-installed on macOS. Good for geometric display headings.
Recipe 4: Scholarly Serif (Editorial / Blog)
:root {
--font-serif: 'Lora', 'Palatino Linotype', Palatino, Georgia, 'Times New Roman', serif;
}
Palatino as a platform fallback gives a refined, humanist feel. Georgia as the universal backup.
Recipe 5: Monospace Code
:root {
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', Menlo, Consolas, 'Courier New', monospace;
}
JetBrains Mono and Fira Code for ligature support. Menlo on macOS, Consolas on Windows, Courier New as the universal fallback.
Recipe 6: Pure System UI (Performance First)
:root {
--font-system: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
This is Tailwind CSS's default font stack — highly compatible, zero downloads, native feel. Emoji-capable via the trailing emoji font entries.
Recipe 7: Transitional Serif (Finance / Legal)
:root {
--font-serif: 'Source Serif 4', 'Cambria', Georgia, 'Times New Roman', Times, serif;
}
Source Serif 4 has excellent OpenType features and optical sizing. Cambria is a well-designed screen serif on Windows.
/* Apply recipes across component types */
body {
font-family: var(--font-sans);
}
.prose, article, .blog-post {
font-family: var(--font-serif);
}
code, pre, kbd {
font-family: var(--font-mono);
}
.hero-headline {
font-family: var(--font-display);
}
A well-constructed font stack is an act of defensive design. Your web font is the intention; your fallbacks are the guarantee that the experience remains good for every user, on every device, at every connection speed.
Typography Terms
Try These Tools
Fonts Mentioned
Designed by Christian Robertson for Google's Material Design ecosystem, this neo-grotesque sans-serif is the most widely used typeface on the web and Android. Its dual-nature design balances mechanical precision with natural reading rhythm, making it equally at home in UI labels and long-form text. The variable font supports width and weight axes alongside Cyrillic, Greek, and extended Latin scripts.
Steve Matteson crafted this humanist sans-serif with upright stress and open apertures that prioritize legibility across screen sizes and resolutions. One of the most-deployed web fonts ever published, it strikes a neutral, professional tone well-suited to body copy, email templates, and web applications. Variable width and weight axes, plus Hebrew and Greek script support, make it a versatile multilingual workhorse.
Rasmus Andersson spent years refining this neo-grotesque specifically for computer screens, optimizing letter spacing, x-height, and stroke contrast for high readability at small sizes on digital displays. An optical size axis (opsz) lets the font automatically adjust its design for captions versus headlines, while the weight axis covers the full range from thin to black. It has become the de facto choice for dashboards, documentation sites, and developer tools worldwide.