How to match fallback fonts via metric comparison
- Step 1Analyse the web font — Drop your web font (e.g. Inter) onto the analyser. Record `units_per_em`, `typo.ascender`, `typo.descender`, and `x_height`. These are the targets the fallback must match.
- Step 2Analyse the fallback font (if you have the file) — If you can grab the system font file (Arial, Helvetica, Times) from your OS, analyse it too and record its `units_per_em` and `x_height`. You need the fallback's x-height to compute `size-adjust`. (System fonts vary by OS — Arial is commonly 2048 UPM, Helvetica 1000.)
- Step 3Compute ascent-override and descent-override — `ascent-override = (web typo.ascender / web units_per_em) × 100%`. `descent-override = (|web typo.descender| / web units_per_em) × 100%`. `line-gap-override = (web typo.line_gap / web units_per_em) × 100%` — usually 0%. These force the fallback's line box to the web font's proportions.
- Step 4Compute size-adjust from x-heights — `size-adjust = (web x_height / web units_per_em) / (fallback x_height / fallback units_per_em) × 100%`. This rescales the fallback so its lowercase optically matches the web font, which keeps line lengths (and so wrapping) stable.
- Step 5Write the fallback @font-face — Declare a local fallback face with the four descriptors. Use [font-face-generator](/font-tools/font-face-generator) for the boilerplate, then add the overrides by hand (the generator builds standard `@font-face` blocks; the override descriptors you append).
- Step 6Order the font stack and verify — Put the overridden fallback as the second entry after the web font. Throttle the network in devtools and watch the swap — done right, text doesn't reflow. The Layout Shift entries in the Performance panel should be near zero for the text region.
Override descriptor → analyser metric it's computed from
The descriptors are CSS; the analyser supplies the raw numbers on the right. The tool does NOT output the descriptors — you compute them.
| @font-face descriptor | Formula | Analyser fields used |
|---|---|---|
ascent-override | web typo.ascender / web units_per_em × 100% | web typo.ascender, units_per_em |
descent-override | |web typo.descender| / web units_per_em × 100% | web typo.descender, units_per_em |
line-gap-override | web typo.line_gap / web units_per_em × 100% | web typo.line_gap, units_per_em |
size-adjust | (web x-height ratio / fallback x-height ratio) × 100% | both fonts' x_height, units_per_em |
Browser support for the override descriptors
All four descriptors are now broadly supported; older Safari ignores them and falls back to the un-adjusted system font (CLS during swap, no breakage).
| Descriptor | Chrome | Firefox | Safari |
|---|---|---|---|
size-adjust | 92+ | 92+ | 17+ |
ascent-override | 87+ | 89+ | 15.4+ |
descent-override | 87+ | 89+ | 15.4+ |
line-gap-override | 87+ | 89+ | 15.4+ |
Worked Inter → Arial override numbers
Illustrative. Analyse your own copies — system Arial metrics differ slightly by OS version. Inter shown at 2048 UPM (its TTF), Arial at 2048 UPM.
| Metric | Inter (web) | Arial (fallback) | Resulting descriptor |
|---|---|---|---|
| units_per_em | 2048 | 2048 | — |
| typo.ascender | 1984 | — | ascent-override: 96.9% |
| typo.descender | -494 | — | descent-override: 24.1% |
| x_height | 1118 | 1062 | size-adjust ≈ 105.3% |
Cookbook
The exact arithmetic that turns analyser output into override descriptors. Numbers are illustrative — substitute your fonts' real values. After computing, assemble the block with font-face-generator; for the broader rhythm context see the vertical-rhythm guide.
ascent-override and descent-override from web-font metrics
ExampleRead the web font; divide its typo ascender/descender by its UPM. These pin the fallback's line box to the web font's.
Analyser on Inter-Regular.ttf: units_per_em = 2048 typo.ascender = 1984 typo.descender = -494 typo.line_gap = 0 ascent-override = 1984 / 2048 × 100 = 96.9% descent-override = 494 / 2048 × 100 = 24.1% line-gap-override = 0 / 2048 × 100 = 0%
size-adjust from the two x-heights
ExampleRead both fonts' x-height ratios; size-adjust is web-ratio over fallback-ratio. It rescales the fallback so its lowercase matches the web font's optical size.
Inter x-height ratio: 1118 / 2048 = 0.5459 Arial x-height ratio: 1062 / 2048 = 0.5186 size-adjust = 0.5459 / 0.5186 × 100 = 105.3% → Arial is rendered ~5.3% larger so its x-height equals Inter's, keeping line lengths and wrapping stable across the swap.
The assembled fallback @font-face
ExampleDrop the four computed descriptors into a local() fallback face, then reference it in the stack right after the web font.
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
ascent-override: 96.9%;
descent-override: 24.1%;
line-gap-override: 0%;
size-adjust: 105.3%;
}
body {
font-family: "Inter", "Inter Fallback", sans-serif;
}When you can't get the fallback file
ExampleIf you can't analyse the system font directly, you can still set ascent/descent overrides (web-font-only) and skip size-adjust, accepting a small horizontal shift.
Web font metrics are enough for the vertical overrides: ascent-override: 96.9%; descent-override: 24.1%; line-gap-override: 0%; size-adjust requires the fallback's x-height — omit it if unknown. Vertical CLS is cancelled; minor horizontal reflow may remain. (Published metric tables for Arial/Helvetica/Times exist if you want to plug in size-adjust without the file.)
Matching a 1000-UPM CFF web font to a 2048-UPM fallback
ExampleMismatched UPM is fine because every term divides by its own font's UPM. The descriptor math normalises it automatically.
Web (CFF): units_per_em 1000, typo.ascender 800, typo.descender -200, x_height 515
Fallback: units_per_em 2048, x_height 1062
ascent-override = 800 / 1000 × 100 = 80%
descent-override = 200 / 1000 × 100 = 20%
size-adjust = (515/1000) / (1062/2048) × 100
= 0.515 / 0.5186 × 100 = 99.3%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.
Web font has no x-height, so size-adjust can't be computed
Partial — vertical overrides onlyIf the web font reports x_height: null, you can't compute size-adjust from the JSON. Set ascent-override/descent-override from the (present) typo metrics to cancel vertical CLS, and either omit size-adjust (minor horizontal reflow remains) or measure the x glyph height in glyph-inspector and use that instead of sxHeight.
typo and win disagree on the web font
By design — pick one basisThe overrides are usually computed from typo metrics, but a USE_TYPO_METRICS-unset font renders with win metrics, so a typo-derived override can still shift on some browsers. The analyser reports both blocks; if you see residual CLS, recompute the ascent/descent overrides from the win numbers (win.ascent / units_per_em, win.descent / units_per_em) and test again.
Old iOS Safari (pre-15.4)
Degrades — overrides ignoredascent-override/descent-override/line-gap-override shipped in Safari 15.4 and size-adjust in Safari 17. Older versions ignore the descriptors entirely and use the raw system fallback, so CLS during swap returns for those users. This is graceful degradation — nothing breaks, the swap just shifts as it would have without overrides.
Fallback file not available to analyse
Workable with published metricsYou often can't extract the exact system Arial/Helvetica from a user's machine, and versions differ. The analyser needs the file to read x-height. Either analyse a copy you do have, use widely published metric tables for the common system fonts, or skip size-adjust and accept minor horizontal reflow while keeping the vertical overrides.
Variable web font
Default-instance metricsThe analyser reports the variable font's default-instance metrics (not MVAR-interpolated per axis). If you load a non-default weight as the primary face and that weight has different metrics, derive your overrides from a frozen instance instead — freeze with variable-font-freezer, analyse the frozen file, then compute the descriptors.
Overrides perfect but line still shifts vertically
Check line-gap and explicit line-heightIf you set line-gap-override: 0% but the web font actually ships a non-zero sTypoLineGap, the line box differs. Read typo.line_gap from the analyser and set line-gap-override to its real ratio, or pin an explicit line-height on the element so neither font's natural gap participates.
size-adjust changes wrapping but not height
Expectedsize-adjust scales the fallback's glyph widths and heights together, which mostly affects horizontal advance and therefore line wrapping. If your only visible CLS is vertical, the ascent/descent overrides do the heavy lifting and size-adjust matters less — though matching x-heights still avoids a visible size pop during the swap.
Using a custom WOFF2 as the fallback instead of local()
SupportedYou can apply the same overrides to a small custom fallback WOFF2 rather than local() a system font. Analyse that WOFF2 directly (the tool decompresses WOFF2 in-browser) for its x-height, compute size-adjust against the web font, and reference it in the stack. This guarantees the same fallback metrics on every OS.
Frequently asked questions
Why not just give the fallback the same line-height?
line-height controls space between lines, not glyph footprint. A web font with a taller ascender occupies more pixels per line than the fallback at the same font-size and line-height, so text still reflows on swap. The override descriptors fix the actual ascent/descent/size mismatch — which is what causes CLS — rather than just the inter-line spacing.
Does this tool output the ascent-override / size-adjust CSS?
No. The analyser reports the raw measurements — units_per_em, ascender, descender, x-height — for each font. You compute the percentage descriptors from those numbers (the formulas and worked examples are above) and write the @font-face yourself. font-face-generator helps assemble the surrounding block.
Can I use real metrics or do I have to estimate?
Use real metrics — estimating produces visible CLS, which defeats the point. Run the analyser on both the web font and the fallback (or use published metric tables for common system fonts you can't extract). Common system UPMs differ — Arial is often 2048, Helvetica 1000 — which is exactly why you read the actual units_per_em rather than assuming.
What about iOS Safari before 15.4?
size-adjust arrived in Safari 17 and the ascent/descent/line-gap overrides in 15.4. Older versions ignore them and use the raw system fallback, so those users see CLS during swap as they would without overrides. It's safe degradation — design the fallback stack so the page is still usable, and most of your traffic on modern browsers gets the zero-shift behaviour.
How do I get the system fallback's metrics if I can't extract the file?
Three options: analyse a copy of the font you do have access to; use widely published metric tables for Arial/Helvetica/Times/Georgia; or skip size-adjust and rely on the ascent/descent overrides (computed from your web font only) to cancel the vertical shift, accepting minor horizontal reflow.
Should I compute the overrides from typo or win metrics?
Start with typo (the designer's intent, what modern browsers use when USE_TYPO_METRICS is set). If you still see CLS on a browser that uses win metrics, recompute ascent/descent from the analyser's win.ascent/win.descent and test. The analyser gives you both blocks so you can switch basis without re-measuring.
Does mismatched UPM between the two fonts break the math?
No. Every term in every descriptor divides by its own font's units_per_em, so a 1000-UPM web font and a 2048-UPM fallback combine correctly. That's why you must read each font's real UPM from the analyser rather than assuming both are 1000 — the ratio is what matters, and it normalises the units.
Is the web font uploaded when I analyse it?
No. The analyser parses in your browser; the result panel shows '0 bytes uploaded'. This matters for licensed or unreleased brand fonts you can't legally send to a third party — you can measure them for fallback matching without them leaving the page.
Can I apply overrides to a custom WOFF2 fallback instead of a system font?
Yes. Point the fallback @font-face at a small custom WOFF2 with src: url(...) and apply the same overrides. Analyse that WOFF2 directly for its x-height to compute size-adjust. The advantage is identical fallback metrics on every OS, removing the system-font variability.
What if the override cancels vertical shift but text still jumps horizontally?
That's a size-adjust (width) issue, not ascent/descent. Compute size-adjust from the two x-height ratios so the fallback's glyph widths match the web font's advance widths more closely. If x-height is missing, measure it in glyph-inspector and use that.
Does this work with variable fonts?
The analyser reads the default-instance metrics. If your primary face is a non-default weight with different metrics, freeze that instance with variable-font-freezer, analyse the frozen file, and compute the overrides from those numbers so the fallback matches the weight you actually render.
Where do I get the surrounding @font-face boilerplate?
Use font-face-generator to produce a standard @font-face block (family, src, weight, display), then append the four override descriptors you computed. The generator handles the structure; the metric-derived overrides are the part you fill in from the analyser's numbers.
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.