|$ curl https://forge-ai.dev/api/markdown?path=docs/css/fonts
$cat docs/@font-face-&-fonts.md
updated Recently·12 min read·published

@font-face & Fonts

CSSFontsTypographyIntermediate
Introduction

Web fonts allow you to use custom typefaces that are not installed on the user's system. The @font-face at-rule defines custom fonts, while properties like font-family, font-weight, and font-display control how they are applied and rendered.

Modern web font technology includes WOFF2 compression, variable fonts (multiple weights/styles in one file), and advanced display strategies to balance performance with visual fidelity. Understanding these tools is essential for building fast, typographically rich web experiences.

font-face-basics.css
CSS
1@font-face {
2 font-family: "Geist";
3 src: url("/fonts/geist.woff2") format("woff2");
4 font-weight: 400;
5 font-style: normal;
6 font-display: swap;
7}
@font-face Rule

The @font-face at-rule defines a custom font family name and points to the font file(s). You can declare multiple @font-face blocks for different weights, styles, and unicode ranges under the same family name.

font-face-declaration.css
CSS
1/* Basic declaration */
2@font-face {
3 font-family: "MyFont"; /* custom family name */
4 src: url("/fonts/myfont.woff2") format("woff2"),
5 url("/fonts/myfont.woff") format("woff"); /* fallback */
6 font-weight: 400;
7 font-style: normal;
8 font-display: swap;
9 unicode-range: U+0000-00FF; /* basic Latin only */
10}
11
12/* Multiple weights under same family */
13@font-face {
14 font-family: "Inter";
15 src: url("/fonts/inter-regular.woff2") format("woff2");
16 font-weight: 400;
17}
18
19@font-face {
20 font-family: "Inter";
21 src: url("/fonts/inter-bold.woff2") format("woff2");
22 font-weight: 700;
23}
24
25/* Italic variant */
26@font-face {
27 font-family: "Inter";
28 src: url("/fonts/inter-italic.woff2") format("woff2");
29 font-weight: 400;
30 font-style: italic;
31}
32
33/* Variable font declaration */
34@font-face {
35 font-family: "InterVariable";
36 src: url("/fonts/inter-variable.woff2") format("woff2");
37 font-weight: 100 900; /* range of weights */
38 font-style: normal;
39}

info

Always provide WOFF2 as the primary format — it offers the best compression (30-50% smaller than WOFF). Include a WOFF fallback for older browsers. Use format("woff2") and format("woff") hints so the browser can choose without downloading unsupported formats.
font-family

font-family specifies a prioritized list of font families. The browser uses the first available font. Always end with a generic family keyword (serif, sans-serif, monospace) as a final fallback.

font-family.css
CSS
1/* System font stack — fastest performance */
2body {
3 font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
4}
5
6/* Modern sans-serif stack */
7.sans {
8 font-family: "Inter", "SF Pro", "Helvetica Neue", Arial, sans-serif;
9}
10
11/* Terminal monospace stack */
12code, pre, .terminal {
13 font-family: "Fira Code", "SF Mono", Menlo, "Cascadia Code", monospace;
14}
15
16/* Serif for body */
17.serif {
18 font-family: "Merriweather", "Georgia", "Times New Roman", serif;
19}
20
21/* Specific fonts */
22/* system-ui — OS native UI font */
23/* -apple-system — macOS/iOS San Francisco */
24/* Segoe UI — Windows */
25/* Roboto — Android/ChromeOS */
preview
font-weight

font-weight controls text thickness. Keywords map to numeric values: normal=400, bold=700. Variable fonts allow any weight between 1 and 1000.

font-weight.css
CSS
1/* Keyword values */
2.normal { font-weight: normal; } /* 400 */
3.bold { font-weight: bold; } /* 700 */
4
5/* Numeric values */
6.thin { font-weight: 100; }
7.extra-light { font-weight: 200; }
8.light { font-weight: 300; }
9.regular { font-weight: 400; }
10.medium { font-weight: 500; }
11.semi-bold { font-weight: 600; }
12.bold-num { font-weight: 700; }
13.extra-bold { font-weight: 800; }
14.black { font-weight: 900; }
15
16/* Variable fonts — any value */
17.variable-weight {
18 font-family: "InterVariable", sans-serif;
19 font-weight: 450; /* between Regular and Medium */
20}
21
22/* Relative keywords */
23.lighter { font-weight: lighter; } /* lighter than parent */
24.bolder { font-weight: bolder; } /* bolder than parent */
25
26/* Terminal use */
27.terminal-title {
28 font-weight: 600;
29 color: #E0E0E0;
30}
31
32.terminal-meta {
33 font-weight: 300;
34 color: #525252;
35}
preview
font-style & font-stretch

font-style controls italic and oblique variants. font-stretch controls condensed/expanded variants (supported mainly by variable fonts).

font-style-stretch.css
CSS
1/* font-style */
2.normal { font-style: normal; }
3.italic { font-style: italic; } /* uses italic variant */
4.oblique { font-style: oblique; } /* artificially slanted */
5.oblique-deg { font-style: oblique 10deg; } /* with angle */
6
7/* font-stretch — condensed to expanded */
8.condensed { font-stretch: condensed; } /* narrower */
9.normal-stretch { font-stretch: normal; } /* regular */
10.expanded-stretch { font-stretch: expanded; } /* wider */
11
12/* Percentage values */
13.stretch-50 { font-stretch: 50%; } /* ultra-condensed */
14.stretch-100 { font-stretch: 100%; } /* normal */
15.stretch-200 { font-stretch: 200%; } /* ultra-expanded */
16
17/* Keyword values */
18.ultra-condensed { font-stretch: ultra-condensed; }
19.extra-condensed { font-stretch: extra-condensed; }
20.semi-condensed { font-stretch: semi-condensed; }
21.semi-expanded { font-stretch: semi-expanded; }
22.extra-expanded { font-stretch: extra-expanded; }
23.ultra-expanded { font-stretch: ultra-expanded; }
24
25/* Variable font axis */
26/* font-stretch maps to 'wdth' axis in variable fonts */
preview
font-display

font-display controls the font loading behavior and the trade-off between displaying text quickly (with a fallback font) and waiting for the custom font. It addresses the FOIT (Flash of Invisible Text) vs FOUT (Flash of Unstyled Text) problem.

font-display.css
CSS
1/* Font display strategies */
2@font-face {
3 font-family: "MyFont";
4 src: url("myfont.woff2") format("woff2");
5
6 font-display: auto; /* browser default — usually short block + swap */
7 font-display: block; /* hide text for ~3s (block), then swap */
8 font-display: swap; /* show fallback immediately, swap when ready */
9 font-display: fallback; /* short block (~100ms), then swap */
10 font-display: optional; /* extremely short block, may never swap */
11}
12
13/* Recommended strategy by use case */
14/* Body text → swap (text visible immediately) */
15/* Brand fonts → block (brand typography important) */
16/* Icon fonts → block (invisible icons = broken UI) */
17/* Decorative → optional (nice to have, not essential) */
18/* Performance-critical → optional or fallback */
ValueBlock PeriodSwap PeriodBest For
autoVaries by browserInfinite (if loads)Default
block~3sInfiniteBrand/icon fonts
swap0InfiniteBody text (best UX)
fallback~100ms~3sBalance
optional~100msNoneNon-critical fonts

best practice

Use font-display: swap for body text to prevent invisible text (FOIT). Reserve font-display: block for icon fonts where invisible icons would break the interface. Use font-display: optional for decorative fonts that are not essential to the design.
Variable Fonts

Variable fonts (OpenType Font Variations) bundle multiple font instances into a single file. Instead of loading separate files for regular, bold, italic, condensed, etc., one variable font file contains all variations along registered axes (weight, width, slant, optical size) and custom axes.

variable-fonts.css
CSS
1/* Declare a variable font */
2@font-face {
3 font-family: "InterVariable";
4 src: url("/fonts/inter-variable.woff2") format("woff2");
5 font-weight: 100 900; /* weight axis range */
6 font-stretch: 75% 125%; /* width axis range */
7 font-style: normal;
8}
9
10/* Use standard CSS properties */
11.title {
12 font-family: "InterVariable", sans-serif;
13 font-weight: 450; /* any value in range */
14 font-stretch: 110%; /* slightly wider */
15}
16
17/* Low-level axis control */
18.creative {
19 font-family: "InterVariable", sans-serif;
20 font-variation-settings:
21 "wght" 450, /* weight */
22 "wdth" 110, /* width */
23 "slnt" -5; /* slant (italic-like) */
24}
25
26/* Registered axes */
27/* wght — weight (1-1000) */
28/* wdth — width (percentage) */
29/* slnt — slant (in degrees) */
30/* ital — italic (0 or 1) */
31/* opsz — optical size (point size) */
32
33/* Custom axes (foundry-specific) */
34/* XHGT — x-height */
35/* YOPQ — y-optic quadrature */
36/* GRAD — grade (weight without width change) */
preview
Google Fonts Integration

Google Fonts provides a vast library of open-source typefaces. While convenient, loading from the Google Fonts CDN introduces an external dependency and potential performance overhead. Self-hosting is preferred for production.

google-fonts.css
CSS
1/* Option 1: Link tag in HTML (simplest) */
2/* <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> */
3
4/* Option 2: @import in CSS (slower — blocks rendering) */
5@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
6
7/* Option 3: Self-hosted (best performance) */
8/* Download, subset, and host the font files yourself */
9@font-face {
10 font-family: "Inter";
11 src: url("/fonts/inter-regular.woff2") format("woff2");
12 font-weight: 400;
13 font-display: swap;
14}
15
16/* Using the font */
17body {
18 font-family: "Inter", system-ui, sans-serif;
19}
20
21/* Google Fonts with variable font support */
22/* <link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,100..900&display=swap" rel="stylesheet"> */
23
24/* Performance tips for Google Fonts */
25/* 1. Preconnect to origin */
26/* <link rel="preconnect" href="https://fonts.googleapis.com"> */
27/* <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> */
28/* 2. Use display=swap parameter */
29/* 3. Limit character subsets (e.g. &subset=latin) */
30/* 4. Limit weight variants requested */

warning

Using @import url() for Google Fonts blocks parallel CSS downloading and delays rendering. Always prefer the <link> tag in HTML with preconnect resource hints. For production, self-host your fonts to eliminate the external dependency.
Local Font Loading

Self-hosting fonts gives you full control over performance, availability, and privacy. The process involves downloading font files, subsetting to needed characters, converting to WOFF2, and serving them from your own domain.

local-font-loading.css
CSS
1/* Self-hosted font structure */
2@font-face {
3 font-family: "MyFont";
4 src: url("/fonts/myfont.woff2") format("woff2");
5 font-weight: 400;
6 font-style: normal;
7 font-display: swap;
8}
9
10/* Subsetting — reduces file size by 70-90% */
11/* Only include characters your content needs */
12/* Tools: glyphhanger, fonttools, Google Fonts subsetter */
13
14/* Font loading strategy */
15/* 1. Preload critical fonts */
16/* <link rel="preload" href="/fonts/myfont.woff2" as="font" crossorigin> */
17/* 2. Use font-display: swap */
18/* 3. Size-match fallback to reduce CLS */
19
20/* Fallback font size adjustment */
21@font-face {
22 font-family: "MyFont-fallback";
23 src: local(system-ui);
24 size-adjust: 105%; /* match custom font metrics */
25 ascent-override: 88%;
26 descent-override: 20%;
27 line-gap-override: 10%;
28}
29
30/* Use fallback */
31body {
32 font-family: "MyFont", "MyFont-fallback", sans-serif;
33}
Performance & FOIT/FOUT

Web fonts significantly impact page performance. The two main rendering issues are FOIT (Flash of Invisible Text — text hidden while font loads) and FOUT (Flash of Unstyled Text — fallback shown, then swapped). Managing these requires strategic font loading.

font-performance.css
CSS
1/* FOIT vs FOUT strategies */
2
3/* Strategy 1: font-display: swap */
4/* Best UX: text visible immediately, swaps when font loads */
5@font-face {
6 font-family: "Body";
7 src: url("body.woff2") format("woff2");
8 font-display: swap;
9}
10
11/* Strategy 2: Font loading API (JavaScript) */
12// Use the CSS Font Loading API for fine control
13// document.fonts.load('1em Inter').then(() => {
14// document.documentElement.classList.add('fonts-loaded');
15// });
16
17/* Strategy 3: Preload critical fonts */
18/* <link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin> */
19/* <link rel="preload" href="/fonts/inter-bold.woff2" as="font" crossorigin> */
20
21/* Strategy 4: Size-match fallback to minimize CLS */
22/* Measure and adjust fallback font metrics */
23@font-face {
24 font-family: "Inter-fallback";
25 src: local(Arial);
26 size-adjust: 107%;
27 ascent-override: 90%;
28 descent-override: 22%;
29}
30
31/* Performance checklist */
32/* 1. Use WOFF2 format (30-50% smaller than WOFF) */
33/* 2. Subset fonts (remove unused characters) */
34/* 3. Preload critical fonts */
35/* 4. Use font-display: swap for body text */
36/* 5. Self-host instead of CDN */
37/* 6. Limit font families to 2-3 per page */
38/* 7. Use variable fonts to reduce file count */
StrategyFOITFOUTCLSComplexity
font-display: swapNoneYesHigh (if metrics mismatch)Low
font-display: optionalNoneNo (if not cached)None (fallback used)Low
Preload + swapMinimalMinimalLowMedium
Size-matched fallbackNoneMinimalMinimalHigh
font-size-adjust

font-size-adjust adjusts the font size to maintain consistent readability when the fallback font has different aspect ratios than the primary font. This minimizes layout shift during font swap.

font-size-adjust.css
CSS
1/* Adjust based on x-height */
2.x-height-adjust {
3 font-size-adjust: 0.5; /* adjust to match x-height ratio */
4}
5
6/* From-ex metric — use the primary font's x-height */
7.body {
8 font-family: "Inter", system-ui, sans-serif;
9
10 /* Prevent layout shift: adjust fallback to match Inter's x-height */
11 /* The x-height ratio of Inter is approximately 0.52 */
12 font-size-adjust: 0.52;
13}
14
15/* Newer syntax with from-font keyword */
16.auto-adjust {
17 font-family: "Inter", system-ui, sans-serif;
18 font-size-adjust: from-font; /* use primary font's metric */
19}
20
21/* Practical: consistent terminal font sizing */
22.terminal {
23 font-family: "Fira Code", "SF Mono", Menlo, monospace;
24 font-size-adjust: 0.48; /* typical monospace x-height */
25}
26
27/* size-adjust in @font-face (modern) */
28@font-face {
29 font-family: "Inter-fallback";
30 src: local(Arial);
31 size-adjust: 107%;
32}
🔥

pro tip

Use size-adjust in @font-face for fallback fonts to dramatically reduce Cumulative Layout Shift (CLS). This is the most effective technique for preventing layout jank during web font loading. Pair with ascent-override and descent-override for precise metric matching.
Best Practices
Self-host fonts for production to eliminate external dependencies and DNS lookups
Use WOFF2 format — smallest file size, best compression
Subset fonts to include only needed characters (e.g., Latin-only)
Use font-display: swap for body text to prevent FOIT
Use variable fonts to reduce HTTP requests (multiple weights in one file)
Preload critical fonts with <link rel=preload> in the HTML head
Size-match fallback fonts with @font-face size-adjust to minimize CLS
Limit font families to 2-3 per page for performance and visual consistency
Use system font stack as a high-performance alternative for UIs that do not require custom typography
Test font loading on slow networks (3G throttling) to verify fallback behavior
🔥

pro tip

The future of web fonts is variable fonts combined with the CSS Font Loading API. Variable fonts provide continuous control over weight, width, and other axes from a single file. The Font Loading API lets you control when and how fonts are applied, enabling custom loading strategies that eliminate both FOIT and FOUT.
$Blueprint — Engineering Documentation·Section ID: CSS-16·Revision: 1.0