Skip to content

Fonts

WAGE automatically handles font loading — preloading, @font-face declarations, and font-display: swap — from two inputs: font files in a folder and font family tokens. All font logic lives in the Wage\Assets class.

The system is token-driven. Wage\Assets::get_fonts() reads the --heading-font-family and --text-font-family tokens, extracts the first font family name, slugifies it, and matches .woff2 files in the child theme’s assets/fonts/ directory.

  1. Token --heading-font-family is set to "'DM Serif Display', Georgia, serif" in the child’s inc/tokens.php
  2. Wage\Assets extracts DM Serif Display from the token value (first name before the comma)
  3. Wage\Assets slugifies it to dm-serif-display
  4. Wage\Assets scans assets/fonts/ for files matching that slug
  5. Wage\Assets matches dm-serif-display-400.woff2 (static) or dm-serif-display.woff2 (variable)
  6. Wage\Assets outputs in <head> via hooks registered by Wage\Assets::init():
    • Wage\Assets::preload_fonts()<link rel="preload"> for each matched file (priority 1)
    • Wage\Assets::output_font_faces()<style id="wage-fonts"> with @font-face declarations using font-display: swap (priority 2)

The font-family name in the @font-face rule comes from the token, not the filename — so it always matches what the CSS expects. No mismatches possible.

The system supports both static and variable font files:

  • Static fonts have a weight in the filename: playfair-display-400.woff2. Each weight is a separate file. The @font-face gets font-weight: 400.
  • Variable fonts have no weight in the filename: inter.woff2. One file contains all weights. The @font-face gets font-weight: 100 900.

Many modern fonts (Inter, Outfit, etc.) are variable. You only need one file instead of separate files per weight. The system detects this automatically from the filename.

TypeFilename@font-face result
Staticdm-serif-display-400.woff2font-weight: 400; font-style: normal
Staticdm-serif-display-700.woff2font-weight: 700; font-style: normal
Variableinter.woff2font-weight: 100 900; font-style: normal
Static italicdm-serif-display-400-italic.woff2font-weight: 400; font-style: italic
Variable italicinter-italic.woff2font-weight: 100 900; font-style: italic

Download .woff2 files. For Google Fonts, use curl with a Chrome user-agent to get woff2 format:

Terminal window
curl -s -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" \
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"

This returns CSS with URLs to the .woff2 files. Download the latin subset URL. If the font is variable (same URL for all weights), you only need one file.

Place .woff2 files in assets/fonts/. The family slug must match the slugified version of the font family name — lowercase, spaces replaced with hyphens.

Static fonts (one file per weight):

{family-slug}-{weight}.woff2
{family-slug}-{weight}-italic.woff2

Variable fonts (one file for all weights):

{family-slug}.woff2
{family-slug}-italic.woff2

Examples:

Font FamilyTypeFiles
InterVariableinter.woff2
Inter (with italic)Variableinter.woff2, inter-italic.woff2
DM Serif DisplayStaticdm-serif-display-400.woff2
DM Serif Display (with italic)Staticdm-serif-display-400.woff2, dm-serif-display-400-italic.woff2
Playfair DisplayStaticplayfair-display-400.woff2, playfair-display-700.woff2
QuicksandVariablequicksand.woff2

In the child theme’s inc/tokens.php, set the font family tokens:

'--heading-font-family' => "'DM Serif Display', Georgia, serif",
'--text-font-family' => "'Inter', system-ui, sans-serif",

Always include fallback fonts after the custom font name.

That’s it. Wage\Assets handles preloading, @font-face, and font-display: swap automatically.

For a project with inter.woff2 (variable) and dm-serif-display-400.woff2 (static), the <head> will contain:

<!-- Preload (priority 1) — Wage\Assets::preload_fonts() -->
<link rel="preload" href=".../dm-serif-display-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href=".../inter.woff2" as="font" type="font/woff2" crossorigin>
<!-- @font-face (priority 2) — Wage\Assets::output_font_faces() -->
<style id="wage-fonts">
@font-face {
font-family: 'DM Serif Display';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('.../dm-serif-display-400.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('.../inter.woff2') format('woff2');
}
</style>

Files that don’t match any token slug are silently ignored. For example, if your token says 'Playfair Display' (slug: playfair-display) but the file is named playfair-400.woff2 (slug: playfair), it won’t match and no @font-face will be generated. The browser will fall back to the next font in the token’s fallback chain.

Core defaults both --heading-font-family and --text-font-family to system sans-serif fonts. With no font files and no token overrides, the site renders in the OS system font — clean and fast. This is also what wireframe mode uses.

Font preloading and @font-face output are skipped entirely in wireframe mode. Wage\Assets::preload_fonts() and Wage\Assets::output_font_faces() both check Wage\Flags::mode() === 'wireframe' and return early. The site renders in system fonts regardless of what font files exist.

Every project self-hosts fonts. No external requests to fonts.googleapis.com. This eliminates:

  • External dependency on Google’s servers
  • Extra DNS lookups and TCP handshakes
  • Privacy concerns (Google tracking via font requests)
  • Flash of unstyled text from late-loading external CSS