How to find your variable-font break-even point in one upload
- Step 1Settle on the weight count under debate — Count the distinct `font-weight` values the design actually needs — e.g. Regular + Semibold + Bold = 3. That's the decision row.
- Step 2Upload the variable font being argued about — Drop the `.ttf`/`.otf`/`.woff`/`.woff2` master with the `fvar` table. WOFF/WOFF2 are decoded internally.
- Step 3Check axes is non-zero — Glance at `axes` in the output. If it's 0, you uploaded a static file by mistake and the comparison is void — re-upload the variable master.
- Step 4Jump to your weight-count row — In `projections`, the entry where `static_weight_count` equals your real weight count holds the verdict.
- Step 5Read variable_wins and delta_bytes — `variable_wins: true` means the single variable file is projected lighter; `delta_bytes` is how many bytes static would cost on top of (or below) variable.
- Step 6Decide and route — Variable wins -> keep it, set up loading with [Font Display Strategy](/font-tools/font-display-strategy) and [Preload Tag Builder](/font-tools/preload-tag-builder). Static wins -> freeze instances with the [Variable Font Freezer](/font-tools/variable-font-freezer).
Decision rule encoded by the tool
With a per-weight estimate of 40% of the variable size, the verdict depends only on weight count.
| Weights you use | Projection | Verdict |
|---|---|---|
| 1 | static total = 40% of variable | Static wins — freeze one instance |
| 2 | static total = 80% of variable | Static wins — freeze two instances |
| 3 | static total = 120% of variable | Variable wins (break-even crossed) |
| 4 | static total = 160% of variable | Variable wins comfortably |
| 5+ | static grows linearly | Variable wins by a widening margin |
Fields that drive (or void) the decision
Two output fields decide; two more tell you whether to trust the upload.
| Field | Role | What to watch for |
|---|---|---|
variable_wins | The verdict | Read it at your weight-count row, not row 0 |
delta_bytes | The stakes | Negative = variable costs that many extra bytes; positive = static would |
axes | Sanity check | 0 means no fvar — you uploaded a static file |
glyph_count | Sanity / tier | Over 1,000 hits the free glyph cap |
Cookbook
Worked decisions for common projects. Byte figures follow strictly from file size x the fixed 40% per-weight model; substitute your own file's size.
Design system with 5 weights: keep variable
ExampleA component library loading Light/Regular/Medium/Bold/Black. The 5-weight row is emphatic.
Upload: Family-Variable.woff2 (variable_file_bytes 300000) estimated_static_per_weight_bytes: 120000 projections[4] (5 weights): estimated_static_total_bytes: 600000 variable_wins: true delta_bytes: 300000 (static would double the payload)
Landing page with 2 weights: ship statics
ExampleA one-off landing page needing only Regular and Bold.
projections[1] (2 weights): estimated_static_total_bytes: 240000 variable_bytes: 300000 variable_wins: false delta_bytes: -60000 (variable wastes 60 KB here)
Exactly at break-even
ExampleThree weights is the model's tipping point — variable_wins flips true.
projections[2] (3 weights): estimated_static_total_bytes: 360000 variable_bytes: 300000 variable_wins: true delta_bytes: 60000 breakeven_static_count: 3
Catching a frozen-instance upload
Exampleaxes:0 voids the verdict — the file has no variation axes.
variable_file_bytes: 92000 axes: 0 glyph_count: 740 # Stop. This is a static instance. Upload the variable master.
Stress-testing future weight growth
ExampleThe 1-9 range lets you see how the verdict holds if the design adds weights later.
delta_bytes by weight count: 1: -180000 2: -60000 3: +60000 4: +180000 5: +300000 ... 9: +780000 # Adding weights only strengthens the case for variable.
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.
No file provided
Error: Upload a variable font.The tool throws before any decision logic runs. Supply the actual font you're deciding about.
Read row 0 instead of your real weight count
MisreadRow 0 is the 1-weight projection and almost always says static wins. The common mistake is reading it as 'variable is never worth it'. Read the row whose static_weight_count matches the weights you load.
Verdict depends on the 40% assumption
Heuristic onlyThe whole comparison rests on each static weight being 40% of the variable size. The tool never weighs real static instances. If your statics are unusually heavy or light, the delta at the margin (around 3 weights) may flip in reality.
Break-even is fixed at 3
By constructionbreakeven_static_count is always 3 because of the 0.4 multiplier; it does not vary by font. It encodes the '3+ weights' rule, not a per-file calculation.
Static file uploaded (axes 0)
axes: 0No fvar table means axes is 0 and the decision is meaningless. Re-upload the variable master.
Uncompressed TTF uploaded instead of served WOFF2
Skews variable bytes upvariable_bytes is the uploaded file size. An uncompressed variable TTF looks bigger than the WOFF2 you serve, making variable look worse than it is. Upload the served artifact.
Over the free 5 MB / 1,000-glyph limits
Rejected (tier limit)Free tier caps at 5 MB and 1,000 glyphs. Larger families need Pro.
Options supplied
IgnoredThere is no weight-count option; options are not read. You always get the full projection and pick your own row.
Comparing two variable files
Not supportedThis tool takes a single font. To compare two fonts side by side, that's a different workflow — analyse each separately.
Unsupported format
Error: Unsupported font formatOnly TTF/OTF/WOFF/WOFF2 (by magic bytes) are accepted; anything else throws.
Frequently asked questions
So when is a variable font worth it?
By this tool's model, at 3 or more weights the single variable file is projected lighter than the equivalent static stack; at 1-2 weights static wins. Read variable_wins at your real weight-count row to confirm.
Where does the '3 weights' number come from?
The per-weight static estimate is 40% of the variable size, so the static stack overtakes the variable file once you hit 3 weights (3 x 40% = 120% > 100%). That's why breakeven_static_count is always 3.
Is the 40% figure measured from real static fonts?
No. It's a fixed heuristic (variable_file_bytes * 0.4). The tool does not generate or weigh real static instances, so treat the verdict as directional, especially right around 3 weights.
Which row should I read?
The projections entry whose static_weight_count equals the number of distinct font-weight values your CSS loads.
What does delta_bytes mean?
estimated_static_total_bytes - variable_bytes. Positive means the static stack would cost that many more bytes (variable wins); negative means variable wastes that many bytes (static wins).
Why does it say static wins when I only use one weight?
Because one static weight (~40% of the variable size) is genuinely smaller than the whole variable file. If you truly use one weight, freeze it.
Can I make it use my exact static sizes?
No — there's no input for that. The model is fixed at 40% per weight. For real static sizes, freeze instances with the Variable Font Freezer and weigh them yourself.
What if axes is 0?
You uploaded a font with no fvar table (a static file). The comparison is void; upload the variable master.
Does input format matter for the verdict?
Yes — variable_bytes is the uploaded file's size. Upload the WOFF2 you actually serve, not an uncompressed TTF, or variable will look unfairly heavy.
Variable wins — how do I ship it well?
Set the swap behaviour with Font Display Strategy and add a <link rel=preload> via the Preload Tag Builder.
Static wins — what's the next step?
Extract the instances you need with the Variable Font Freezer, then subset each with the Font Subsetter.
Are there usage limits?
Free tier: 5 MB file, 1,000 glyphs, one file at a time. Pro raises these to 50 MB and 65,536 glyphs.
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.