Web Fonts and Browser Font Selection

Last update: 2024-09-03 19:44:16 +0800 +0800
tags: Css | Fonts | Performance | Web
Available translations: 日本語 / 中文(简体) / 中文(繁體)

A typeface, or font family, groups related faces. A face has properties such as weight, style, and stretch. A glyph is the rendered shape selected for a character or character sequence.

Browser font selection happens per character. A paragraph can therefore use one family for Latin letters, another for CJK characters, and an emoji font for emoji.

Build a Font Family Stack #

font-family is an ordered list of family names and generic families:

css
body {
  font-family:
    "Example Sans",
    "Noto Sans",
    system-ui,
    sans-serif;
}

Quote family names that contain whitespace. End the stack with a generic family because local font availability differs by operating system.

Generic families include serif, sans-serif, monospace, cursive, fantasy, system-ui, ui-serif, ui-sans-serif, ui-monospace, ui-rounded, emoji, math, and fangsong. Support for newer generic names varies, so retain a broad final fallback.

For each character, the browser considers available faces, requested style, weight, and stretch, then falls back when the chosen face lacks a glyph. Fallback can change metrics within one line; test the scripts that production content contains.

Declare Downloadable Faces #

@font-face associates a family and face range with a resource:

css
@font-face {
  font-family: "Example Sans";
  src: url("/fonts/example-sans-latin.woff2") format("woff2");
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
}

The declaration does not apply the font. A later font-family declaration selects it.

Declare italic and upright faces separately unless the file contains the corresponding variable axis:

css
@font-face {
  font-family: "Example Sans";
  src: url("/fonts/example-sans-italic.woff2") format("woff2");
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
}

Accurate descriptors prevent the browser from synthesizing bold or italic forms when a real face exists.

Set font-synthesis: none only when synthetic bold, italic, or small caps would violate the design and every required face is available. Otherwise synthesis can preserve emphasis when a selected font lacks the requested face.

Understand Web Font Formats #

Modern sites should prefer WOFF2. It packages OpenType or TrueType font data with compression designed for web delivery.

FormatRole
TrueType (.ttf)Desktop and platform font format using TrueType outlines
OpenType (.otf)Font container supporting several outline and layout technologies
WOFFWeb container with compression and metadata
WOFF2Newer web container with stronger compression
EOTHistorical Internet Explorer web format
SVG fontsObsolete text-font mechanism; SVG remains useful for icons and graphics

Format conversion does not grant redistribution rights. Verify the font’s license before self-hosting or subsetting it.

Use Variable Fonts Deliberately #

A variable font stores a range of designs in one file. Registered axes include weight (wght), width (wdth), optical size (opsz), slant (slnt), and italic (ital).

css
@font-face {
  font-family: "Example Variable";
  src: url("/fonts/example-variable.woff2") format("woff2");
  font-weight: 300 800;
  font-stretch: 75% 125%;
  font-style: normal;
}

Use high-level properties such as font-weight and font-stretch for registered axes. font-variation-settings is appropriate for custom axes or precise low-level control.

A variable face participates in the same family matching algorithm as a static face. A request inside a declared axis range selects an interpolated instance. When the request lies outside the range, matching uses the nearest supported value or another better-matching face in the family; if the selected face lacks a character, selection continues through the fallback family list for that character. Declare accurate ranges so the browser does not select a face that cannot produce the requested instance.

A variable file is not automatically smaller than every static-font set. It is efficient when the page needs enough of the represented range to offset the additional variation data.

Control Loading and Layout Shift #

font-display defines fallback behavior:

  • block permits a short invisible-text period before fallback.
  • swap displays fallback immediately and swaps when the face arrives.
  • fallback uses a short block and limited swap period.
  • optional allows the browser to keep fallback under constrained conditions.

swap preserves readable text, but a metric mismatch can shift layout. Choose a fallback with similar proportions or use metric overrides:

css
@font-face {
  font-family: "Example Sans Fallback";
  src: local("Arial");
  size-adjust: 102%;
  ascent-override: 92%;
  descent-override: 24%;
  line-gap-override: 0%;
}

Derive override values from measured font metrics rather than guessing.

Preload only a face needed during initial rendering:

html
<link
  rel="preload"
  href="/fonts/example-sans-latin.woff2"
  as="font"
  type="font/woff2"
  crossorigin>

The preload URL, CORS mode, and @font-face URL must match or the browser can download the resource twice. Over-preloading competes with critical CSS, scripts, and images.

Subset by Character Coverage #

unicode-range lets the browser download a face only when text requires its code points:

css
@font-face {
  font-family: "Example Sans";
  src: url("/fonts/example-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

Subsets must collectively cover combining marks, punctuation, symbols, and all supported languages. A missing combining mark can produce a fallback glyph that does not align with the base character.

Local Font Access #

window.queryLocalFonts() can enumerate locally installed fonts after user permission in supporting browsers:

js
const fonts = await window.queryLocalFonts();
const matching = fonts.filter((font) => font.family === "Noto Sans");

Font enumeration exposes identifying information and therefore has strict permission and availability constraints. Do not make ordinary page rendering depend on it; CSS font matching already uses installed fonts named in the family stack.

The local() source in @font-face also exposes installed-font availability to CSS matching. A browser can ignore local sources or otherwise reduce their precision for privacy, so provide a downloadable source after any local hint:

css
@font-face {
  font-family: "Example Sans";
  src:
    local("Example Sans"),
    url("/fonts/example-sans.woff2") format("woff2");
}

Do not use long lists of local font probes as a fingerprinting mechanism.

Comments

Read or comment on GitHub (sign-in required to comment)