Web Fonts
Web fonts let you use typefaces beyond those installed on the visitor's device. They're loaded with @font-face or from a service like Google Fonts — and loading them well matters, because fonts can delay text from appearing.
Using a hosted font service
<title>Fonts</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap">
body {
font-family: "Inter", system-ui, sans-serif;
}
The service provides the CSS; you reference the family name in your own CSS, with fallbacks. The preconnect links start the connection early, and display=swap shows fallback text while the font loads.
Self-hosting with @font-face
@font-face {
font-family: "Brand Sans";
src: url("/fonts/brand-sans.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Brand Sans";
src: url("/fonts/brand-sans-italic.woff2") format("woff2");
font-weight: 400 700;
font-style: italic;
font-display: swap;
}
body {
font-family: "Brand Sans", system-ui, sans-serif;
}
@font-face defines a family name and where to download it. Several rules with the same family name but different weights or styles form one family; the browser downloads only the files a page actually uses. A range like font-weight: 400 700 declares a variable font that covers every weight in between.
Self-hosting avoids third-party requests (better for privacy and often speed), and many open-source fonts allow it.
File formats
Use WOFF2: it's compressed and supported by every current browser. Older formats (WOFF, TTF, EOT, SVG fonts) are only needed for very old browsers.
font-display: avoiding invisible text
| Value | While the font loads… |
|---|---|
swap | Show fallback text immediately, swap when the font arrives. Good for body text |
optional | Use the font only if it's ready almost instantly; otherwise keep the fallback (no layout shift). Good for performance |
fallback | A short invisible period, then fallback; swap only if it arrives soon |
block | Hide text for up to ~3 seconds. Only for icon fonts |
Reducing layout shift
When the web font arrives and replaces the fallback, text can reflow if the two fonts have different sizes. Descriptors on a fallback @font-face adjust a local font to match:
@font-face {
font-family: "Brand Sans Fallback";
src: local("Arial");
size-adjust: 104%;
ascent-override: 92%;
}
body {
font-family: "Brand Sans", "Brand Sans Fallback", sans-serif;
}
Framework tools (like Next.js's font loader) generate these values automatically.
Preloading the most important font
<title>Preload</title> <link rel="preload" href="/fonts/brand-sans.woff2" as="font" type="font/woff2" crossorigin>
Preloading tells the browser to fetch the font before it discovers it in the CSS. Preload only the one or two fonts used above the fold — crossorigin is required even for fonts on your own domain.
Keep it lean
- Limit families and weights: every file is another download. A variable font often replaces several static weights.
- Subset fonts to the characters you need (for example, only Latin), using
unicode-rangeor a subsetting tool. - Consider system font stacks — they're instant and look native on every device.
Icon fonts vs SVG icons
Icon fonts were popular, but inline SVG icons are now preferred: they're sharper, more accessible, can be multi-coloured, and don't fail invisibly when the font doesn't load.
Try it yourself
This example loads a font from Google Fonts using @import (simpler for a demo; <link> loads faster in real pages):