How to tighten the wght axis to your real weight range
- Step 1Upload the variable font — Drop your TTF/OTF/WOFF/WOFF2 variable font. The file is read in-browser and decompressed if needed before the `fvar` table is parsed.
- Step 2Locate the wght row — Find the axis whose `tag` is `wght`. Read its `current_min`/`current_max` (declared) and `recommended_min`/`recommended_max` (from named instances).
- Step 3Compare against your CSS weights — Check that every weight your design system sets falls inside the recommended range. Because the recommendation comes from named instances — not your CSS — confirm it covers your real usage before trusting it.
- Step 4Read the savings percentage — The wght row's `range_savings_pct` tells you how much of the declared weight span the trim removes. A high number on wght usually translates to a meaningful WOFF2 reduction.
- Step 5Copy the command and adjust if needed — Take the `wght=min:max` fragment from `fonttools_command`. If your CSS uses a weight outside the named-instance span, widen the fragment by hand before running it.
- Step 6Run it and rebuild your @font-face — Run the fontTools command, re-compress with [ttf-to-woff2](/font-tools/ttf-to-woff2), and regenerate your range-based @font-face with [font-face-generator](/font-tools/font-face-generator), updating `font-weight` to the new `min max`.
Common weight-axis trims
Typical declared-vs-used weight ranges and the savings the formula produces. Recommended ranges assume the named instances match the listed weights.
| Declared wght range | Named-instance span | Recommended range | range_savings_pct |
|---|---|---|---|
| 100–900 | 400–700 | 400–700 | round((1 − 300/800)×100) = 63% |
| 100–900 | 300–800 | 300–800 | round((1 − 500/800)×100) = 38% |
| 1–1000 | 400–700 | 400–700 | round((1 − 300/999)×100) = 70% |
| 100–900 | 100–900 | 100–900 | 0% (full ladder) |
| 100–900 | single instance at 400 | 400–400 | round((1 − 0/800)×100) = 100% |
Mapping the trim back to CSS
After instancing, update your variable @font-face to advertise the new range so the browser knows the true bounds.
| Before (declared) | After (trimmed) | @font-face change |
|---|---|---|
font-weight: 100 900; | wght=400:700 | font-weight: 400 700; |
font-variation-settings: "wght" 350; (out of new range) | wght=400:700 | Raise to 400, or widen the trim to wght=350:700 |
| Range advertised wider than file | — | Browsers clamp to the file's real range; advertise the trimmed bounds to avoid surprises |
Cookbook
Weight-axis trims from real design-system scenarios. The recommendation always follows the named instances baked into the font.
Four-weight design system on a full-ladder font
ExampleYour system uses 400/500/600/700, but the font ships named instances from 100 to 900. Because the instances span the full declared range, the optimiser recommends 100–900 and reports 0% — the font's own instance ladder is wider than your CSS, and the tool follows the instances.
fvar wght: min 100 default 400 max 900 instances: Thin 100 … Black 900 recommended_min = 100, recommended_max = 900 range_savings_pct = 0 → To trim to your real 400–700, edit the command: fonttools varLib.instancer wght=400:700 Brand.ttf
Font whose instances match your usage
ExampleA leaner font ships only Regular (400) and Bold (700) as named instances. The optimiser recommends 400–700 directly and the emitted command needs no edit.
fvar wght: min 100 default 400 max 900 instances: Regular 400, Bold 700 recommended_min = 400, recommended_max = 700 range_savings_pct = round((1 − 300/800) × 100) = 63 command: fonttools varLib.instancer wght=400:700 Brand.ttf
Pinning weight to a single value
ExampleIf the only named instance is Regular, the used-value set has one entry, so recommended_min equals recommended_max and the axis pins to 400 — a single-weight static in effect.
instances: Regular 400 (only one) usedValues = [400] → recommended_min = recommended_max = 400 range_savings_pct = round((1 − 0/800) × 100) = 100 command fragment: wght=400:400 (If you want a real range, prefer the freezer or widen the fragment.)
Updating @font-face after the trim
ExampleOnce instanced to 400–700, advertise the new range in CSS so the browser and any font-weight animations stay within bounds.
/* before */
@font-face { font-family: Brand; src: url(brand.woff2) format("woff2-variations"); font-weight: 100 900; }
/* after fonttools varLib.instancer wght=400:700 + re-compress */
@font-face { font-family: Brand; src: url(brand-400-700.woff2) format("woff2-variations"); font-weight: 400 700; }Confirming the byte win
ExampleThe savings percentage measures axis-range coverage, not bytes. Compare WOFF2 sizes before and after to see the real reduction on your specific font.
before: Brand.var.woff2 → 198 KB run: fonttools varLib.instancer wght=400:700 Brand.ttf after: Brand-400-700.var.woff2 → 141 KB reported wght savings: 63% (range) | actual file saving: 29%
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.
Named instances wider than your CSS usage
Recommends the wider instance rangeThe recommendation follows the font's named instances, not your stylesheet. A font with a full Thin-to-Black ladder recommends 100–900 even if your CSS only uses 400–700. Edit the emitted wght=min:max fragment to your real range before running fontTools.
Only one named instance on the weight axis
Pins wght to a single valueA single used value makes recommended_min == recommended_max, pinning the weight axis to a point — effectively a static single weight. If you need a true range, widen the fragment or use variable-font-freezer.
No fvar table
Error: not a variable fontA static weight file throws This font has no fvar table — it isn't a variable font. There's no wght axis to trim on a static font.
No file uploaded
Error: upload requiredThrows Upload a variable font. The tool needs a file every time.
Weight axis already tight
0% savingsIf the named instances span the full declared wght range, the row reports 0%. Trimming would gain nothing, and the tool says so rather than inventing a saving.
Advertising a wider range than the trimmed file
Browser clamps silentlyAfter trimming to 400–700, leaving font-weight: 100 900 in CSS makes browsers clamp out-of-range values to the file's real bounds. Update the @font-face range to match the trim to keep behaviour predictable.
Italic delivered as a separate axis
Reported as its own rowSome families expose italics via a separate ital or slnt axis rather than a second file. The optimiser reports those axes too; trimming wght won't touch them, so pin or keep them deliberately.
Custom weight-like axis with a non-wght tag
Not treated as weightOnly the axis tagged exactly wght is the CSS font-weight axis. A bespoke weight axis under a different tag is reported separately and won't map to font-weight automatically.
Frequently asked questions
Will trimming the weight axis change how my existing weights look?
No, as long as every weight you set in CSS falls within the trimmed range. Pinning only removes variation data outside the kept span; weights inside it render identically.
Why does the tool recommend a wider range than my CSS uses?
Because it derives the recommendation from the font's named instances, not your stylesheet. If the instance ladder is wider than your usage, edit the emitted wght=min:max fragment down to your real range before running fontTools.
Does it actually remove the unused weights from the file?
Not in the browser. It outputs a fonttools varLib.instancer command; running that on the desktop produces the trimmed font. Then re-compress with ttf-to-woff2.
How much smaller will my font get?
The range_savings_pct is a range-coverage figure. The real WOFF2 saving is usually somewhat lower because gvar deltas don't scale perfectly linearly. Re-measure to confirm — a 63% range trim might yield ~30% bytes.
Should I trim or just freeze to one weight?
If you need a single weight, freeze it with variable-font-freezer. If you need a range (e.g. 400–700 for body and headings), trim the axis instead and keep the variable file.
What do I change in my @font-face after trimming?
Update the font-weight range to match the new bounds — e.g. font-weight: 400 700; — and point src at the re-compressed file. Regenerate the block with font-face-generator.
What if my font has no named instances?
Then the weight axis has no used values and pins to its default — a single weight. That's likely not what you want for a design system; widen the command fragment or freeze deliberately.
Does this handle width and slant too?
Yes — every axis is reported. Trimming weight is the focus here, but you'll see wdth, slnt, and any custom axes in the same report so you can pin them together in one command.
What tier do I need?
Pro. File-size limits are 5 MB (Free), 50 MB (Pro), 1 GB (Developer).
Is my unreleased brand font safe?
Yes. The fvar parse runs entirely in your browser; the font is never uploaded to a server.
Can browsers go outside my advertised weight range?
No — they clamp font-weight and font-variation-settings to the file's real bounds. After trimming, advertise the trimmed range so authors aren't surprised by clamped values.
What's the output of the tool?
A JSON report (<font>.axis-ranges.json) with per-axis ranges, savings, and the fontTools command. No font file is produced by the tool itself.
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.