Web Fonts

Difficulty: Intermediate

Web fonts allow you to use custom typefaces that are not installed on the user's device. Before web fonts, designers were limited to a small set of web-safe fonts (Arial, Times New Roman, Georgia, Verdana, etc.) that were reliably installed across operating systems. The @font-face rule, introduced in CSS3, changed this by allowing fonts to be downloaded from a server and used on the page.

The @font-face rule defines a custom font by specifying the font family name and the source files. The src property accepts multiple formats for browser compatibility. The modern approach is to use WOFF2 (Web Open Font Format 2) as the primary format -- it offers the best compression (30-50% smaller than WOFF) and is supported by all modern browsers. WOFF is the fallback for older browsers. TTF and OTF are desktop font formats that work in browsers but are significantly larger and should only be used as last-resort fallbacks.

Google Fonts is the most popular free web font service, hosting over 1,500 font families. You can include Google Fonts via a <link> tag in HTML or an @import in CSS. However, using Google Fonts via their CDN means the user's browser must connect to Google's servers, which adds latency. Self-hosting the font files (downloading them and serving from your own domain) eliminates this extra connection, reduces DNS lookups, and gives you more control over caching. Tools like google-webfonts-helper make self-hosting easy.

The font-display property controls how text is rendered while the font is loading. This is critical for performance and user experience. The most common values are swap (shows fallback text immediately, swaps to custom font when loaded -- causes a visible flash but text is always readable), fallback (brief invisible period, then fallback, swaps if font loads quickly), optional (brief invisible period, then may or may not use the custom font based on connection speed), and block (hides text until font loads -- can cause Flash of Invisible Text). font-display: swap is the most widely recommended for body text.

Variable fonts are a modern font technology that packages multiple styles (weights, widths, italics) into a single font file. Instead of loading separate files for Regular, Bold, and Italic, a variable font contains continuous axes that can be adjusted to any value. A variable font with a weight axis can produce any weight from 100 to 900, not just the predefined steps. This dramatically reduces the number of HTTP requests and total file size when using multiple font variations.

Code examples

Self-Hosted @font-face

/* Define custom font with multiple formats */
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-regular.woff2") format("woff2"),
       url("/fonts/inter-regular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-bold.woff2") format("woff2"),
       url("/fonts/inter-bold.woff") format("woff");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Use the custom font */
body {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
}

Each @font-face rule defines one weight/style combination. WOFF2 is listed first for best compression. font-display: swap ensures text is visible immediately using a fallback, then swaps to the custom font once loaded. Always include system fonts as fallbacks.

Google Fonts Integration

<!-- Method 1: HTML link (recommended for performance) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" 
      rel="stylesheet">

<!-- Method 2: CSS @import (blocks rendering) -->
<style>
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
</style>

<!-- Usage -->
<style>
  body {
    font-family: "Roboto", sans-serif;
  }
</style>

The <link> method with preconnect hints is faster than @import because @import blocks CSS parsing. The preconnect tells the browser to establish connections to Google's font servers early. Only request the weights you actually use to minimize download size.

font-display Values Compared

@font-face {
  font-family: "MyFont";
  src: url("/fonts/myfont.woff2") format("woff2");
  
  /* swap: Show fallback immediately, swap when loaded */
  /* Best for body text -- text is always visible */
  font-display: swap;
}

@font-face {
  font-family: "HeadingFont";
  src: url("/fonts/heading.woff2") format("woff2");
  
  /* optional: Use if loaded quickly, skip otherwise */
  /* Best for non-critical decorative fonts */
  font-display: optional;
}

@font-face {
  font-family: "IconFont";
  src: url("/fonts/icons.woff2") format("woff2");
  
  /* block: Hide text until font loads (up to 3s) */
  /* Only appropriate for icon fonts where fallback is meaningless */
  font-display: block;
}

swap is the default recommendation: text is always visible with a brief flash when the font loads. optional is great for non-essential fonts on slow connections -- the browser may skip loading entirely. block should rarely be used as it hides text, but is appropriate for icon fonts.

Variable Fonts

/* Single variable font file replaces multiple static files */
@font-face {
  font-family: "Inter Variable";
  src: url("/fonts/Inter-Variable.woff2") format("woff2-variations");
  font-weight: 100 900; /* supports any weight in this range */
  font-style: normal;
  font-display: swap;
}

/* Use any weight value, not just 100/200/300 etc */
.thin     { font-weight: 100; }
.light    { font-weight: 300; }
.regular  { font-weight: 400; }
.medium   { font-weight: 450; } /* variable font can do this! */
.semibold { font-weight: 600; }
.bold     { font-weight: 700; }
.heavy    { font-weight: 850; } /* variable font can do this! */

/* Custom axes via font-variation-settings */
.custom {
  font-variation-settings: "wght" 625, "wdth" 85;
}

Variable fonts use font-weight: 100 900 to declare the supported range. Unlike static fonts that only support specific steps, variable fonts render any weight smoothly. font-variation-settings provides access to custom axes like width (wdth), optical size (opsz), and designer-defined axes.

Key points

Concepts covered

@font-face, Google Fonts, font-display, Performance, Variable Fonts, Self-Hosting