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:
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:
@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:
@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.
| Format | Role |
|---|---|
TrueType (.ttf) | Desktop and platform font format using TrueType outlines |
OpenType (.otf) | Font container supporting several outline and layout technologies |
| WOFF | Web container with compression and metadata |
| WOFF2 | Newer web container with stronger compression |
| EOT | Historical Internet Explorer web format |
| SVG fonts | Obsolete 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).
@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:
blockpermits a short invisible-text period before fallback.swapdisplays fallback immediately and swaps when the face arrives.fallbackuses a short block and limited swap period.optionalallows 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:
@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:
<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:
@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:
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:
@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)