How to the modern @font-face playbook for 2026
- Step 1Default to a WOFF2-only src list — In the [generator](/font-tools/font-face-generator), tick only WOFF2. That yields `src: url("…woff2") format("woff2");` — the right baseline. Add WOFF only if your analytics show real IE9–11 or no-Brotli WebView traffic; the WOFF checkbox handles it. Never re-add EOT or SVG.
- Step 2Set the font-display policy per face — Branding faces (logo wordmark, hero, headlines): `font-display: swap` — visible immediately on a fallback, swaps when ready. Body text: consider `font-display: optional` so a late-arriving font doesn't reflow paragraphs mid-read. Both are in the generator's Display select.
- Step 3Subset and scope with unicode-range — For multilingual sites, ship one file per script and give each block a `unicode-range`. The browser only downloads the subset whose range matches rendered characters. Build the subset files with the [Font Subsetter](/font-tools/font-subsetter), confirm coverage with the [Character Coverage Map](/font-tools/character-coverage-map), then paste each range into the generator's unicode-range field.
- Step 4Add the zero-CLS fallback block by hand — Define a second `@font-face` that re-points the system font to your family name with `size-adjust`, `ascent-override`, and `descent-override` tuned so the fallback's line box matches the web font's. The generator doesn't write these — read the web font's metrics with the [Font Metrics Analyzer](/font-tools/font-metrics-analyzer) and add the descriptors manually.
- Step 5Preload exactly the LCP-critical font — If a font is on the Largest Contentful Paint path (hero type, body on a text page), preload it — and only it. Preloading several fonts competes for early bandwidth and can hurt LCP. Build the `<link rel="preload">` tag with the [Preload Tag Builder](/font-tools/preload-tag-builder).
- Step 6Verify hints, order, and paths before shipping — Confirm the `src` list is WOFF2-first, every hint matches its extension (`truetype` not `ttf`), `font-display` is present, and the file paths resolve. Generating instead of hand-writing covers the first three automatically; the path check is on you — a 404 silently falls back to system fonts.
2026 checklist — and who owns each line
Each modern best practice mapped to whether the @font-face Generator produces it or you add it by hand. Browser support figures are current as of 2026.
| Best practice | Generator emits it? | How / where |
|---|---|---|
WOFF2-only src | Yes | Tick only WOFF2 → single format("woff2") entry |
| WOFF fallback (IE9–11) | Yes (optional) | Tick WOFF; adds format("woff") 2nd |
Correct format() hints + order | Yes | Always WOFF2→WOFF→TTF with right strings |
font-display: swap / optional | Yes | Display select — all five values |
unicode-range subsetting | Yes | unicode-range field, emitted verbatim |
size-adjust | No | Hand-add from web-font metrics |
ascent-override / descent-override / line-gap-override | No | Hand-add for zero-CLS fallback block |
Variable font-weight: 100 900 | No | Hand-edit; or freeze instances |
<link rel=preload> | No (separate tool) | Preload Tag Builder |
| EOT / SVG fallbacks | No (by design) | Don't ship — obsolete |
font-display value — when to use which
All five values are selectable in the generator. The right choice depends on whether late text swap helps or hurts the reader.
| Value | Behaviour | Best for |
|---|---|---|
swap | Fallback shown immediately; swaps to web font whenever it loads | Branding, headlines, logo wordmarks — brand must appear |
optional | Web font used only if it loads almost instantly (or is cached); else fallback for the session | Body text — avoids jarring mid-read reflow |
fallback | Tiny block period, short swap window, then sticks with fallback if late | Compromise between swap and optional |
block | Up to ~3s invisible text waiting for the web font, then swaps | Icon fonts where the fallback glyph is meaningless |
auto | Browser default (typically behaves like block) | Rarely the right explicit choice — pick one of the above |
Cookbook
The modern blocks, ready to paste. The generator produces the core block; the hand-added lines are marked so you know the boundary.
The 2026 baseline block
ExampleWOFF2-only, swap, one weight. This is the entire modern default for a branding face — generated, nothing to hand-edit.
Generator: Family Inter, path /fonts/inter, WOFF2 only, swap →
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}Body text with font-display: optional
ExampleFor long-form reading, optional avoids a late reflow. Same generator run, Display set to optional.
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: optional;
}Zero-CLS: generated face + hand-added fallback override
ExampleThe generator makes the web-font block. You add a second block that re-shapes the system fallback to match its metrics, so nothing jumps on swap. The override descriptors are NOT generated.
/* Generated by the @font-face Generator: */
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-weight: 400; font-style: normal; font-display: swap;
}
/* Hand-added — metric-matched fallback (values from Font Metrics Analyzer): */
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
/* font-family: "Inter", "Inter Fallback", sans-serif; */Multilingual: two blocks, two unicode-ranges
ExampleOne family, two subset files, two ranges. The browser fetches Latin for English pages and Cyrillic only when Cyrillic text renders. Both blocks come from the generator.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin.woff2") format("woff2");
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+2000-206F;
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter-cyrillic.woff2") format("woff2");
font-display: swap;
unicode-range: U+0400-04FF, U+0500-052F;
}Preload the one LCP-critical font
ExamplePreload pairs with the @font-face block but is a separate <head> tag, built by the Preload Tag Builder. Preload exactly one critical font per page.
<!-- in <head>, before the stylesheet that references it -->
<link rel="preload" href="/fonts/inter.woff2"
as="font" type="font/woff2" crossorigin>
<!-- Then the generated @font-face block loads it without a second round-trip -->Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Dropping the WOFF fallback entirely
Safe for mostWOFF2-only is fine for ~99% of traffic. The risk is real only if your audience includes IE9–11 or old no-Brotli embedded WebViews — check analytics first. If that slice matters, tick WOFF in the generator; otherwise WOFF2-only saves a request and is the correct 2026 default.
Expecting size-adjust from the generator
Not generatedThe generator never writes size-adjust or the metric-override descriptors. Zero-CLS requires a second, hand-written fallback @font-face whose size-adjust/ascent-override/descent-override you compute from the web font's metrics (read them with the Font Metrics Analyzer). Don't assume the generated block alone eliminates layout shift.
Preloading several fonts for safety
Hurts LCPPreloading more than the one LCP-critical font makes early requests compete for bandwidth and can delay the very paint you're trying to speed up. Preload exactly one font per page — the one on the LCP path — and let the rest load normally. Build the tag with the Preload Tag Builder.
font-display: optional but the font rarely shows
Expected trade-offoptional only uses the web font if it loads almost instantly (or is cached); on a slow first visit the reader sees the fallback for the whole session. That's the deliberate trade — no reflow, at the cost of brand fonts sometimes not appearing first-paint. Reserve it for body text; use swap where the brand face must show.
Variable font for the whole weight range
Hand-edit neededA single variable WOFF2 can serve every weight with font-weight: 100 900;, but the generator's Weight input is a single number and can't express a range. Hand-edit the generated block to a range, or freeze named instances with the Variable Font Freezer and generate one static block each.
Self-hosting Google Fonts
Different toolIf you're pulling from Google's CDN, the Google Fonts CSS Generator builds the import for you. To self-host (better privacy and performance), download the files, then use the @font-face Generator per face. Don't mix a Google @import with self-hosted blocks for the same family — pick one.
MIME type wrong on the server
fails to loadA correct block still fails if the server returns application/octet-stream or a 404 for the .woff2. The generator can't see your server config. Confirm fonts are served as font/woff2 and reachable at the exact paths — otherwise the page silently falls back to system fonts and CLS work is wasted.
FOIT on font-display: block for body text
Avoid for bodyblock hides text for up to ~3 seconds waiting for the web font (FOIT). That's acceptable for icon fonts (a fallback glyph is meaningless) but bad for readable text. Use swap or optional for body; reserve block for icon faces only.
unicode-range on a single-language site
UnnecessaryIf you only render one script, there's nothing to scope — adding unicode-range just complicates the block without saving anything. Leave the generator's unicode-range field blank. It earns its keep only when you ship multiple subset files of the same family.
Shipping the old EOT/SVG bulletproof list
obsoleteThe 2014 five-format list ships dead files (EOT for IE6–8, SVG fonts removed from browsers) and slows src parsing. The generator deliberately offers neither EOT nor SVG. Strip them from any legacy CSS you inherit — the modern floor is WOFF2, plus WOFF only if IE9–11 traffic is real.
Frequently asked questions
Is WOFF2-only really safe in 2026?
For roughly 99% of traffic, yes — every modern browser has supported WOFF2 since ~2016. The missing slice (IE9–11, some ancient WebViews) simply sees your system-font fallback, which is a graceful degradation, not a breakage. Check your analytics; if that slice is meaningful, tick WOFF in the generator for an IE9–11 fallback. Otherwise WOFF2-only is the right default.
swap or optional — which font-display should I use?
swap for branding faces (the brand must appear, even if it swaps in late) and optional for body text (where a late swap reflows paragraphs mid-read and annoys readers). Both are in the generator's Display select, along with auto, block, and fallback. Set it per face based on whether late text replacement helps or hurts.
Does the generator eliminate layout shift on its own?
No. The generated block sets font-display, which prevents invisible text, but eliminating the layout *shift* when the fallback swaps to the web font requires a second, hand-written fallback @font-face with size-adjust and the metric-override descriptors. The generator doesn't produce those — compute them from the font's metrics (use the Font Metrics Analyzer).
How many fonts should I preload?
One per page — the font on the Largest Contentful Paint path. Preloading more makes early requests compete for bandwidth and can delay the paint you're optimising. Preload is a separate <link> tag, not part of the @font-face block; build it with the Preload Tag Builder.
Should I still write the old five-format bulletproof src list?
No. EOT (IE6–8) and SVG fonts are dead, and shipping them wastes bytes and slows src parsing. The modern list is WOFF2, plus WOFF only if IE9–11 traffic is real. The generator offers exactly those (WOFF2/WOFF/TTF checkboxes) and never EOT or SVG, which keeps you on the 2026 path automatically.
Why isn't size-adjust in the @font-face Generator?
Because the right size-adjust (and ascent-override/descent-override/line-gap-override) value depends on the *specific* web font's metrics relative to the chosen fallback — it can't be guessed from the generic block options. The generator focuses on the universally-correct core block; you compute the metric overrides from your font and add them to a separate fallback block. The descriptors reference lists the descriptors.
How do I do zero-CLS in practice?
Two blocks. First, the generated web-font block (swap). Second, a hand-written fallback block that maps local("Arial") (or your system fallback) to a fallback family name with size-adjust/ascent-override/descent-override tuned to the web font's metrics. Then list both in font-family: "Inter", "Inter Fallback", sans-serif;. The fallback occupies the web font's space, so swap causes no jump.
What about variable fonts?
A variable WOFF2 can cover the whole weight range with font-weight: 100 900;, but the generator's Weight field is a single number, so you'd hand-edit the block to a range. Alternatively, freeze the named instances you actually ship with the Variable Font Freezer and generate one static block per instance — simpler if you only use a few weights.
Self-host or use the Google Fonts CDN?
Self-hosting is generally better for privacy (no third-party request) and can be faster (no extra DNS/connection). Download the files and generate a block per face with the @font-face Generator. If you're staying on Google's CDN, use the Google Fonts CSS Generator instead. Don't mix the two for one family.
Do I need unicode-range for an English-only site?
No. unicode-range scopes a block to a character subset so the browser fetches only the matching file — useful only when you ship multiple subset files of one family for multiple scripts. For a single-language site there's nothing to scope, so leave the generator's unicode-range field empty.
How do I confirm my modern block actually works?
Check four things: the src is WOFF2-first, every format() hint matches its extension (truetype not ttf), font-display is present, and the file URLs resolve with a font/woff2 MIME type. The generator guarantees the first three; the path/MIME check is on your server. A 404 silently falls back to system fonts.
Is the 2026 checklist different for icon fonts?
Slightly. Icon fonts are the one case where font-display: block makes sense — a fallback glyph for an icon is meaningless, so a brief invisible period beats a wrong glyph. Everything else (WOFF2-only, correct hints, no EOT/SVG) still applies. Set the generator's Display to block for icon faces and swap/optional for text faces.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.