How to use named instances to recommend tighter axis ranges
- Step 1Upload the variable font — Drop the font in. The `fvar` table is parsed in-browser, including both the axis records and the named-instance coordinate arrays.
- Step 2Understand which instances exist — The recommendation is only as good as the instance ladder. The recommended ranges come straight from the named-instance coordinates, so know your instances before trusting the numbers. If you only need a single instance baked to static, the [variable-font-freezer](/font-tools/variable-font-freezer) is the direct route.
- Step 3Read each axis's instance-derived range — For every axis, compare `current_min`/`current_max` to `recommended_min`/`recommended_max`. The recommended values are literally the smallest and largest coordinate the named instances use on that axis.
- Step 4Spot the default-pinned axes — Any axis where `recommended_min == recommended_max == default` means no named instance moved it. The tool pins it. Confirm you don't set that axis in CSS before accepting the pin.
- Step 5Decide trust vs override — If your CSS usage is a subset of the instance ladder, trust the command. If you use coordinates the instances don't cover, edit the `tag=min:max` fragment to widen that axis.
- Step 6Run the command — Execute the emitted `fonttools varLib.instancer` command on the desktop, then re-compress to WOFF2 with [ttf-to-woff2](/font-tools/ttf-to-woff2) and re-host. The tool itself only produces the JSON report and command — never a font file.
How the recommendation is computed per axis
The exact rule the optimiser applies to every axis. usedValues is the set of that axis's coordinate across all named instances.
| Condition | recommended_min | recommended_max |
|---|---|---|
| usedValues is non-empty | min(usedValues) | max(usedValues) |
| usedValues is empty (no instance sets the axis) | axis default | axis default |
| Only one instance sets the axis | that single value | that single value (pinned) |
| Instances span the full declared range | current_min | current_max (0% savings) |
Worked savings from an instance ladder
Same declared wght 100–900 axis, three different instance ladders, three different recommendations.
| Named-instance wght values | Recommended range | range_savings_pct |
|---|---|---|
| 100, 200, …, 900 (full ladder) | 100–900 | 0% |
| 400, 700 | 400–700 | round((1 − 300/800)×100) = 63% |
| 400 only | 400–400 | round((1 − 0/800)×100) = 100% |
| none set wght | default–default | 100% (range collapses to a point) |
Cookbook
Reading the instance ladder to predict the recommendation before you even upload. The rule is mechanical: min and max of the per-axis instance coordinates.
Two instances drive a tight weight range
ExampleA font ships exactly two named instances — Regular and Bold. Their wght coordinates (400 and 700) become the recommended min and max. No CSS is consulted; the instances are the whole story.
named instances:
Regular { wght: 400 }
Bold { wght: 700 }
usedValues(wght) = [400, 700]
recommended_min = 400, recommended_max = 700
range_savings_pct = 63 (declared 100–900)An axis no instance moves pins to default
ExampleThe font's instances all leave wdth at 100 (the default). The used-value set for wdth is empty of non-default movement; both recommendations fall to the default and the axis pins.
fvar wdth: min 75 default 100 max 125 instances: none set wdth (all at default 100) usedValues(wdth) = [] → recommended_min = recommended_max = 100 range = 125 − 75 = 50 range_savings_pct = round((1 − 0/50) × 100) = 100 fragment: wdth=100:100
Instances spanning the whole axis: nothing to do
ExampleA font with a complete Thin-to-Black ladder has instances at every declared weight extreme. The recommendation equals the declared range and the saving is 0.
fvar wght: min 100 default 400 max 900 instances: Thin 100 … Black 900 usedValues(wght) = [100, …, 900] recommended_min = 100, recommended_max = 900 range_savings_pct = 0
Multi-axis instances
ExampleInstances that set both wght and wdth contribute to both axes' used-value sets independently. The optimiser handles each axis on its own.
instances:
Cond Light { wght: 300, wdth: 75 }
Wide Bold { wght: 700, wdth: 125 }
wght: usedValues [300,700] → 300:700
wdth: usedValues [75,125] → 75:125
command: fonttools varLib.instancer wght=300:700 wdth=75:125 Font.ttfOverriding when your CSS exceeds the ladder
ExampleYour CSS animates wght down to 200 for a hairline effect, but no named instance goes below 300. Trust the logic, then widen the fragment by hand so your animation stays in range.
recommended (from instances): wght=300:700 your CSS uses: font-variation-settings: "wght" 200 edit before running: fonttools varLib.instancer wght=200:700 Font.ttf
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.
Font has axes but zero named instances
Every axis pins to defaultWith no named instances, every axis's used-value set is empty, so all recommendations collapse to the axis default. The command pins the whole design space to defaults — a full freeze. Use variable-font-freezer if that's the intent.
Instance coordinate missing for an axis
Treated as not-setIf an instance's coordinate for an axis is null/absent, it's filtered out of the used-value set. An axis only enters the recommendation when at least one instance provides a real coordinate for it.
No fvar table
Error: not a variable fontThrows This font has no fvar table — it isn't a variable font. No fvar means no axes and no instances to read.
No file uploaded
Error: upload requiredThrows Upload a variable font. A file is mandatory for every run.
Single-instance font
Pins every axis the instance setsWith one named instance, every axis it sets has a one-element used-value set, so min equals max and each such axis pins. Axes the lone instance leaves at default also pin. The result is effectively a static instance.
Your usage is wider than the instance ladder
Override requiredBecause the recommendation ignores your CSS, animating an axis beyond the instance span will clip after instancing. Widen the relevant tag=min:max fragment before running fontTools.
Instance name records absent
Falls back to Instance NIf an instance's subfamily name record is missing, it's labelled Instance 1, Instance 2, etc. The coordinates — what the recommendation depends on — are still read correctly.
Axis name records absent
Tag used as the nameWhen an axis lacks an English name record, the report's name field shows the raw tag. Display only — the recommendation math is unchanged.
Frequently asked questions
What exactly drives the recommended range?
The named instances in the fvar table. For each axis, recommended_min is the smallest coordinate any named instance uses and recommended_max is the largest. Nothing else — not your CSS, not any option — affects it.
What happens if no instance sets an axis?
Its used-value set is empty, so both recommendations fall back to the axis default and the axis is pinned to that single point in the emitted command.
Why doesn't it look at my CSS?
The tool has no CSS input. It's a pure fvar reader. If your usage differs from the instance ladder, edit the emitted fonttools varLib.instancer fragment for that axis before running it.
How do I see the instances first?
Map the axes and named instances before optimising, then run this tool. Knowing the ladder lets you predict the recommendation: it's just the min and max coordinate per axis across the instances.
Why is my saving exactly 100% on one axis?
Because that axis has either a single used value or none, so the recommended range collapses to a point. round((1 − 0/declaredRange) × 100) is 100% whenever the trimmed range is zero and the declared range is non-zero.
Can a font report 0% on every axis?
Yes — if every axis's instances span its full declared range. That's a sign the font is already as tight as its design space, and there's nothing for the optimiser to trim.
Does the tool ever change the font?
No. It outputs a JSON report and a fonttools varLib.instancer command. The trimming happens when you run that command with Python's fontTools on the desktop, after which you re-compress with ttf-to-woff2 and confirm coverage with character-coverage-map.
What if an instance is missing a name?
It's labelled Instance N in the report, but its coordinates are still read and still drive the recommendation. The name is cosmetic.
Single-instance fonts — what do I get?
Every axis the lone instance sets pins to that value, and untouched axes pin to default. The result is essentially a single static instance; the variable-font-freezer is the more direct tool for that.
What tier is required and what are the size limits?
Pro tier. File-size limits: 5 MB Free, 50 MB Pro, 1 GB Developer.
Is the font uploaded anywhere?
No. The fvar parse runs locally in your browser; the font never reaches a server.
What format is the output?
application/json, saved as <font-stem>.axis-ranges.json, containing the per-axis array, the fontTools command, and a note about why trimming runs on the desktop.
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.