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

HTML google-fonts.html
<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&amp;display=swap">
CSS use-font.css
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

CSS font-face.css
@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

ValueWhile the font loads…
swapShow fallback text immediately, swap when the font arrives. Good for body text
optionalUse the font only if it's ready almost instantly; otherwise keep the fallback (no layout shift). Good for performance
fallbackA short invisible period, then fallback; swap only if it arrives soon
blockHide 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:

CSS fallback-metrics.css
@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

HTML preload.html
<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-range or 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):

Try it yourself
Result