A typeface that looks beautiful in a design tool is a starting point. The version that ships to users is processed, compressed, subsetted, and served with the right CSS hints. Get the pipeline right and a sophisticated multi-weight family loads in under 100 KB and renders without flash or shift. Get it wrong and the same family ships at 800 KB, blocks rendering for two seconds, and shifts the layout when it finally arrives.

This guide covers the formats that matter for production web work, the conversion and subsetting tools that produce the smallest reliable files, and the CSS patterns that keep typography fast, accessible, and consistent across operating systems.

The Format Landscape in 2026

Five font formats appear in the wild on the web. Only one matters for new work.

FormatStatusWhen to Use
WOFF2StandardAlways for new web deployment
WOFFLegacySkip; modern browsers all support WOFF2
TTF / OTFSource formatEdit and store source files, convert to WOFF2 for web
EOTObsoleteInternet Explorer only, retired in 2022
SVG fontsObsoleteRemoved from Chrome and Firefox; never use
WOFF2, defined by W3C Recommendation in 2018, applies Brotli compression to OpenType data and consistently produces files 25 to 35 percent smaller than WOFF. Browser support is universal: every browser released since 2015 reads WOFF2 natively.
"Shipping fonts in 2026 means shipping WOFF2. Anything else is a workaround for users who do not exist anymore. The same minute spent on triple-format fallbacks would be better spent on subsetting." Bram Stein, formerly of Adobe Typekit

From Desktop Source to Web Asset

A typical conversion pipeline takes a TTF or OTF source and emits a subsetted WOFF2 ready to deploy. The reference toolchain is fonttools, the Python library maintained by Google's Type team. Its pyftsubset command does subsetting and WOFF2 conversion in a single pass.

pip install fonttools brotli zopfli

pyftsubset Inter.ttf \
  --output-file=Inter.subset.woff2 \
  --flavor=woff2 \
  --layout-features='*' \
  --unicodes='U+0000-007F,U+00A0-00FF,U+0152-0153,U+2000-206F,U+2074,U+20AC,U+2122,U+2191-2193,U+2212,U+FB01-FB02'

The unicodes argument lists Latin Basic, Latin-1 Supplement, common typographic punctuation, currency symbols, and the fi and fl ligatures. For a Latin-only site, this trims an Inter file from 320 KB to roughly 35 KB without losing any glyph the page actually renders.

Glyphhanger automates the unicode discovery step by scanning a website for the characters actually present.

npm install -g glyphhanger
glyphhanger https://example.com --subset=*.ttf --formats=woff2

For multilingual sites, subset per language or per character range and use the unicode-range descriptor in @font-face to load only the relevant subset.

Variable Fonts: One File, Every Weight

A variable font packs an entire family into a single file by storing interpolation deltas along design axes (weight, width, optical size, slant). A single Inter Variable file at roughly 320 KB replaces nine static weight files totalling 1.6 MB. Browser support shipped in 2018 and now covers more than 99 percent of users.

@font-face {
  font-family: 'InterVariable';
  src: url('/fonts/InterVariable.woff2') format('woff2-variations'),
       url('/fonts/InterVariable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: oblique 0deg 10deg;
  font-display: swap;
}

h1 { font-family: InterVariable; font-weight: 750; }
p  { font-family: InterVariable; font-weight: 400; }

The font-weight range is declared once. CSS pulls the weight it needs from the same file. Subsetted Inter Variable typically ships at 60 to 90 KB, which is competitive with a single static weight in WOFF2.

"Variable fonts are not a typographic novelty. They are how the web should have shipped fonts from the beginning. One file, every weight, perfect interpolation, smaller than a single PNG hero image." David Berlow, type designer at Font Bureau

Variable families to know: Inter, Recursive, Roboto Flex, Source Serif 4, IBM Plex Sans, Fraunces, Mona Sans. All free, all permissively licensed, all small enough to ship without apology.

The Self-Hosting Question

Hosted font services (Google Fonts, Adobe Fonts, Cloud.typography) trade a small performance hit for convenience. They add a DNS lookup, a TLS handshake, and a round trip to a third-party server. They also send the visitor's IP address to that server, which since 2022 has been ruled a GDPR concern in several European jurisdictions.

The self-hosting calculus is straightforward. Host the WOFF2 on your own domain. Use the cache-control header for one-year immutable caching. Preload the critical font.

Cache-Control: public, max-age=31536000, immutable
<link rel="preload" href="/fonts/InterVariable.woff2" as="font" type="font/woff2" crossorigin>

The crossorigin attribute is required for fonts even when same-origin because the CSS Font Loading API treats them as anonymous CORS requests. Omitting it causes the browser to fetch the font twice.

The compliance discussion at Corpy covers GDPR consent rules in plain language for businesses choosing hosting strategies, and the asset workflows at File Converter Free handle TTF/OTF to WOFF2 conversion when a designer hands off source files in the wrong format.

Preventing Flash and Shift

Two visible failures plague poorly configured web fonts.

FOIT (flash of invisible text) shows blank space where text should be while the font loads. Pages appear unresponsive on slow networks. The fix is font-display: swap, which renders fallback type immediately and swaps to the web font when ready.

CLS (cumulative layout shift) happens when the swap causes lines to reflow because the web font has different metrics than the fallback. Modern CSS provides metric override descriptors that match the fallback to the web font's metrics, eliminating the visible jump.

@font-face {
  font-family: 'InterFallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'InterVariable', 'InterFallback', sans-serif;
}

The override values are calculated to make Arial render at the same height and line spacing as Inter. Tools such as Malte Ubl's font-style-matcher and the Capsize library compute the right values automatically.

"Layout shift caused by font swap is a solved problem. The size-adjust descriptor and friends mean a careful designer can ship custom type with zero visible jump. There is no excuse to skip this in 2026." Stephen Nixon, type designer at ArrowType

Subsetting Strategy

Subsetting is the highest-leverage optimisation for fonts, often more impactful than format choice. The decision is which characters to include.

StrategyUse CaseTrade-off
Latin Basic onlyEnglish-only, no special punctuationSmall but breaks on em-dash, smart quotes
Latin extendedWestern European languagesAdds 5-10 KB
Custom range from glyphhangerSite-specific contentSmallest file, content drift breaks it
unicode-range per scriptMultilingual sitesMore complex CSS, browsers fetch on demand
Full character setEditor or publishing appLargest file, no risk of missing glyph
For most marketing and content sites, the right answer is Latin extended plus typographic punctuation, currency symbols, common ligatures, and arrows. The resulting file covers 99 percent of real-world content and stays under 80 KB for a variable font.

For multilingual sites the unicode-range pattern shines.

@font-face {
  font-family: 'InterVariable';
  src: url('/fonts/Inter-latin.woff2') format('woff2');
  unicode-range: U+0000-024F, U+1E00-1EFF, U+2000-206F;
}
@font-face {
  font-family: 'InterVariable';
  src: url('/fonts/Inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0400-04FF, U+0500-052F;
}

Browsers parse the @font-face rules eagerly but only download the subsets needed for characters present on the page. A Latin-only article fetches only the Latin file. A Russian article fetches only the Cyrillic file.

Hinting and Cross-Platform Rendering

Hinting is the set of instructions inside a TrueType font that tells rasterisers how to render the outline at small sizes. Modern browsers on Windows still respect TrueType hints. macOS and iOS largely ignore them. Linux renders with FreeType, which has its own hinting subtleties.

For body text below 16 px, hinted fonts often render more crisply on Windows than unhinted ones. Above 18 px, hinting matters less because subpixel rendering and resolution dominate.

The practical rule: choose families designed for screen use. Inter, Roboto, Source Sans, IBM Plex, and Atkinson Hyperlegible all rendered well across platforms because they were designed for screens from the start. Print-first faces such as Garamond or Bodoni often need 18 px or larger to render comfortably on Windows displays.

"Hinting is a cliff. Either it is well done and the font sings at 12 px, or it is missing and you start fights with Windows ClearType. Modern web designers should pick fonts whose authors hinted them for screens." Erik Spiekermann, Stop Stealing Sheep

Font Loading API for Critical Path

For pages where typography is part of the brand and must render before the user interacts, the CSS Font Loading API provides explicit control.

<script>
  if ("fonts" in document) {
    const inter = new FontFace('InterVariable', 'url(/fonts/InterVariable.woff2)', {
      weight: '100 900',
      display: 'swap'
    });
    inter.load().then(face => {
      document.fonts.add(face);
      document.documentElement.classList.add('fonts-loaded');
    });
  }
</script>

The fonts-loaded class lets CSS apply tighter spacing once the web font is available, smoothing the transition further than the metric overrides alone.

For most sites the simpler approach (preload tag plus font-display: swap plus metric overrides) is enough. The Font Loading API is for cases where typography is the visual centrepiece of the brand.

Performance Budget for Fonts

A practical performance budget keeps font weight bounded.

Site TypeFont BudgetNumber of Files
Marketing landing page60 KBOne variable file
Content site100 KBTwo variable files (sans plus serif)
Editorial site150 KBTwo variable plus icon font
Web app80 KBOne variable, system fallback for body
Documentation site120 KBOne variable plus monospace for code
Sites that exceed these budgets are almost always loading static weights they could replace with a variable font, multiple weights they do not actually use, or icon fonts that should be SVG. Fix one and the budget falls into place.

The audit rhythm described at When Notes Fly catches font drift quarterly, and the typography fundamentals at Evolang help editors choose families that work at small sizes without fighting the rasteriser.

Common Mistakes That Tank Font Performance

Three mistakes recur in audits.

First, loading TTF directly with src: url('font.ttf'). TTF is 30 percent larger than WOFF2 and was never intended for web delivery. Convert to WOFF2.

Second, loading nine static weights and three styles when the design uses three weights. Open DevTools, sort by font, and remove anything not actually rendered. Most sites can drop 60 to 80 percent of font weight this way.

Third, forgetting the crossorigin attribute on the preload tag. Without it, the preload fetches the font and the @font-face rule fetches it again. The browser ends up loading the same file twice and the preload provides no benefit.

Each fix takes minutes. Together they shift LCP visibly on font-heavy pages and improve perceived speed across the site.

The Licensing Question

Web fonts are licensed differently than desktop fonts. A licence to use a typeface in print or a desktop application does not extend to web embedding. Foundries typically charge a separate web licence priced on monthly pageviews.

Open-licence families (SIL Open Font License, Apache, custom permissive licences from Google Fonts) sidestep the licensing question entirely. Inter, Roboto Flex, Source Serif, IBM Plex, Atkinson Hyperlegible, Manrope, and Public Sans are all free for commercial use including web embedding. The quality gap that once justified premium foundry licences has largely closed for general-purpose work.

For brand typography, premium licences still earn their cost. Foundries such as Commercial Type, Klim Type Foundry, and Grilli Type ship faces that no open-licence project matches for character and craft. Budget accordingly and document the licence terms in the engineering wiki so future deploys do not accidentally violate the agreement.

A Production Checklist

Before shipping a font to production, verify each item.

  • Source TTF or OTF stored in version control or asset manager
  • WOFF2 generated with pyftsubset or fonttools, layout features preserved
  • Subset to the unicode ranges actually needed
  • Self-hosted with one-year immutable cache headers
  • Preload tag with crossorigin attribute on critical fonts
  • font-display: swap on every @font-face
  • Metric override descriptors matched to a system fallback
  • Variable font used wherever the family ships one
  • DevTools confirms no extra weights loaded
  • Lighthouse confirms no font-related layout shift

This checklist is short enough to apply to every deploy and catches the regressions that show up after redesigns and content migrations.

Build Pipeline Integration

Font conversion belongs in the build pipeline, not in a designer's local toolchain. Hand-conversion produces inconsistent results across team members, drifts over time, and creates ambiguity about which version of the asset is canonical. The right pattern is to commit source TTF or OTF to the repository and produce WOFF2 derivatives during the build.

A typical Node.js or Vite-based project uses a small build script.

import { glob } from 'glob';
import { writeFileSync } from 'fs';
import { execSync } from 'child_process';

const sources = await glob('src/fonts/*.ttf');
for (const src of sources) {
  const subset = src.replace('/src/fonts/', '/dist/fonts/').replace('.ttf', '.subset.woff2');
  execSync(`pyftsubset ${src} --output-file=${subset} --flavor=woff2 --layout-features='*' --unicodes='U+0000-024F,U+1E00-1EFF,U+2000-206F'`);
}

For projects on Astro, Next.js, or Nuxt, framework-specific font plugins (next/font, nuxt-fonts, astro-font) handle subsetting and self-hosting automatically. These plugins also generate the metric-override CSS for fallback fonts, which removes one of the more error-prone manual steps.

Build pipelines that emit fonts to a hashed-filename CDN path benefit from one-year immutable caching. The hashed path changes only when the font itself changes, so the browser cache survives every deploy that does not touch the font.

Variable Font Authoring Considerations

For teams that maintain custom fonts (in-house brand typefaces, modified open-source families), the variable-font authoring workflow is worth understanding even when the font work itself is delegated to a foundry.

The source format for variable fonts is typically a designspace document plus a set of UFO masters. fontmake compiles the designspace to a binary variable font. The compilation step is deterministic and can run in CI, which means brand fonts can be version-controlled and updated without round-trips to a designer for every change.

Updating a single weight in a static-master family means re-exporting all weights to keep the family consistent. Updating a variable font means recompiling once. The maintenance cost difference compounds over years for active brand systems.

Right-To-Left and Complex Script Considerations

Latin scripts are the easy case. Right-to-left scripts (Arabic, Hebrew), complex scripts (Devanagari, Tamil, Thai), and CJK scripts each impose additional requirements that affect format and conversion choices.

Arabic and Hebrew need contextual glyph forms (initial, medial, final, isolated) carried in the GSUB table. Subsetters that strip GSUB break Arabic rendering completely. Always preserve layout features when working with non-Latin scripts.

Devanagari and other Brahmic scripts depend on conjunct formation and vowel reordering, both implemented through OpenType layout features. Fonts that lack the relevant features (akhn, blwf, half, pstf, vatu, ssub, blws, abvs, psts) render visibly broken even if every glyph is present.

CJK fonts are large. A full Chinese character set covers 20,000 glyphs or more. Subsetting matters more for CJK than any other script. The unicode-range pattern combined with per-page content scanning can reduce a CJK font from 5 MB to 200 KB for a typical page that uses 600 distinct characters.

For multilingual sites, the practical pattern is one variable font per script, loaded conditionally via unicode-range. Browsers parse all @font-face declarations but fetch only the subsets needed for characters present on the page.

Performance Testing Across Real Devices

Synthetic Lighthouse scores from a single development laptop miss the long tail of devices that real users have. Font performance specifically varies with device CPU because rasterisation cost scales with character count and glyph complexity.

Three device categories deserve testing. A current high-end phone (iPhone 15, Pixel 8) represents the modern baseline. A mid-range phone three years old (iPhone 11, Pixel 5a) represents the median user. A low-end phone with a slow CPU and 3G or slow 4G connection represents the long tail in many markets.

WebPageTest's mobile testing locations and BrowserStack's real-device cloud both provide access to a range of devices for testing. Run a font-heavy page through each tier and compare LCP, CLS, and time to interactive. Variable fonts often win on the mid-range tier because the single-file fetch reduces network round trips, even though decode time is comparable.

The audit cadence at When Notes Fly covers how small teams sustain quarterly device testing without it becoming an unbounded engineering project.

References

  1. World Wide Web Consortium. (2018). WOFF File Format 2.0. https://www.w3.org/TR/WOFF2/
  1. World Wide Web Consortium. (2024). CSS Fonts Module Level 4. https://www.w3.org/TR/css-fonts-4/
  1. Google Developers. (2024). Best practices for fonts. https://web.dev/articles/font-best-practices
  1. Berlow, D. (2016). The Font of the Future. Type Network. https://typenetwork.com/
  1. Spiekermann, E. (2014). Stop Stealing Sheep and Find Out How Type Works. Adobe Press. https://www.peachpit.com/store/stop-stealing-sheep-and-find-out-how-type-works-9780321934284
  1. fonttools project. (2024). pyftsubset documentation. https://fonttools.readthedocs.io/en/latest/subset/
  1. European Data Protection Board. (2022). Guidelines on third-party content and GDPR. https://edpb.europa.eu/
  1. Microsoft Typography. (2024). DirectWrite documentation. https://learn.microsoft.com/en-us/windows/win32/directwrite/