@font-face Optimization
Write optimal @font-face declarations for maximum performance. Master format order, local() fallbacks, unicode-range splitting, and font descriptor optimization.
Key Takeaways
- • List WOFF2 first in src for best compression
- • Use local() to check for installed system fonts
- • Always include font-display: swap
- • Use unicode-range for language-based splitting
In this article
The Optimal @font-face Declaration
Here is a complete, optimized @font-face rule with all recommended properties.
@font-face {
/* Font identification */
font-family: 'CustomFont';
/* Source stack - order matters! */
src: local('Custom Font'), /* Check system first */
local('CustomFont'), /* Alternative name */
url('/fonts/custom.woff2') format('woff2'), /* Best format */
url('/fonts/custom.woff') format('woff'); /* Fallback */
/* Weight and style */
font-weight: 400;
font-style: normal;
/* Loading behavior */
font-display: swap;
/* Character subset (optional) */
unicode-range: U+0000-00FF;
}Format Order: WOFF2 First
Browsers download the first format they support. List formats from best to fallback.
/* Correct order - WOFF2 first (30% smaller) */
src: url('/fonts/font.woff2') format('woff2'),
url('/fonts/font.woff') format('woff');
/* For maximum compatibility (rarely needed) */
src: url('/fonts/font.woff2') format('woff2'),
url('/fonts/font.woff') format('woff'),
url('/fonts/font.ttf') format('truetype');
/* Modern browsers only (recommended) */
src: url('/fonts/font.woff2') format('woff2');Format Support
| Format | Browser Support | Recommendation |
|---|---|---|
| woff2 | 96%+ global | Always use |
| woff | 98%+ global | Fallback only |
| ttf/otf | Legacy | Rarely needed |
| eot | IE only | Skip unless IE needed |
Using local() for System Fonts
The local() function checks if a font is already installed on the user's system, avoiding unnecessary downloads.
@font-face {
font-family: 'Open Sans';
src: local('Open Sans'), /* Full name */
local('OpenSans'), /* PostScript name */
local('Open Sans Regular'), /* With weight */
url('/fonts/opensans.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}When to Use local()
- • Popular fonts (Open Sans, Roboto)
- • System fonts with web versions
- • Fonts bundled with OS
When to Skip local()
- • Custom/proprietary fonts
- • When version control matters
- • Privacy concerns (fingerprinting)
Font Weight and Style Declarations
Declare explicit weights and styles to prevent browser faux-bold/italic synthesis.
/* Each weight needs its own @font-face */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* Variable font - weight range */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-variable.woff2') format('woff2-variations');
font-weight: 100 900; /* Range */
font-style: normal;
font-display: swap;
}Unicode-Range Splitting
Split fonts by character set so browsers only download needed subsets.
/* Latin characters - loads for English */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153;
font-display: swap;
}
/* Cyrillic - only loads for Russian, etc. */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-cyrillic.woff2') format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1;
font-display: swap;
}
/* Greek - only loads when Greek chars used */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-greek.woff2') format('woff2');
unicode-range: U+0370-03FF;
font-display: swap;
}Google Fonts Approach
Google Fonts automatically splits fonts into ~20 subsets per language. This is why their CSS files contain many @font-face rules.
Fallback Font Matching
Use font metric overrides to match fallback fonts to your custom font, preventing layout shift.
/* Adjusted fallback to match custom font */
@font-face {
font-family: 'CustomFont-Fallback';
src: local('Arial');
size-adjust: 105%; /* Scale to match */
ascent-override: 90%; /* Adjust ascenders */
descent-override: 22%; /* Adjust descenders */
line-gap-override: 0%; /* Adjust line gap */
}
body {
font-family: 'CustomFont', 'CustomFont-Fallback', Arial, sans-serif;
}Metric Override Properties
size-adjust- Scale the font up/down to match x-heightascent-override- Adjust space above baselinedescent-override- Adjust space below baselineline-gap-override- Adjust extra line spacing
Complete Example
/* Optimized font-face for a typical website */
/* Primary font - Regular */
@font-face {
font-family: 'Inter';
src: local('Inter'),
url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF;
}
/* Primary font - Medium */
@font-face {
font-family: 'Inter';
src: local('Inter Medium'),
url('/fonts/inter-medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF;
}
/* Primary font - Bold */
@font-face {
font-family: 'Inter';
src: local('Inter Bold'),
url('/fonts/inter-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF;
}
/* Adjusted fallback */
@font-face {
font-family: 'Inter-Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}
/* Usage */
body {
font-family: 'Inter', 'Inter-Fallback', system-ui, sans-serif;
}Additional Resources
Generate @font-face CSS
Use our CSS generator to create optimal @font-face declarations automatically.
Open CSS GeneratorWritten & Verified by
Sarah Mitchell
Product Designer, Font Specialist
@font-face Optimization FAQs
Common questions about CSS font declarations
