How to audit the axes of a google fonts or self-hosted variable font
- Step 1Download the variable font you intend to self-host — Whether it is a Google Fonts ZIP, a foundry WOFF2, or an API-streamed file, save the actual file you will put in your project. That exact file is what you should audit — not a different build of the same family.
- Step 2Drop it into the mapper — Upload the WOFF2, TTF, or OTF. WOFF2 is decompressed in-browser to read the fvar table, so you can audit the production web file directly without converting it first.
- Step 3Verify the axis count and ranges — Read axis_count and each axis's min/default/max. A multi-axis family that audits as a single wght axis, or a wght range narrower than expected, means you downloaded a slice rather than the full variable build.
- Step 4Confirm the named instances you need exist — Check the instances list for the styles your design uses. Each instance shows its coordinates, so you can confirm "Bold" really is wght 700 in this particular file.
- Step 5Copy the font-variation-settings for your CSS — Use the css_examples block for the instance you want in your self-hosted @font-face usage. For continuous effects, write values inside the audited ranges.
- Step 6Keep the JSON as a self-hosting record — Download `<fontname>.axes.json` and commit it with the font in your repo. If you ever refresh the file, re-audit and diff to catch a changed build before it breaks production.
Why a self-hosted file may differ from the preview
Common reasons the audited axes don't match what the family's web page suggests. The mapper shows you what the actual file contains.
| Symptom in the audit | Likely cause | What to do |
|---|---|---|
| Only a wght axis, family has more | Requested a single-axis slice via the API | Re-download the full variable build (all axes) |
| wght range narrower than expected | API clamped the axis to a sub-range | Request the full range or grab the foundry's TTF |
| No named instances at all | Slice/subset dropped the instance records | Use the full file; build CSS from axis ranges meanwhile |
| Axis names show as tags | Name records not Windows-English | Cosmetic only; tags are canonical for CSS |
| File parses but isn't variable | Got a static weight instead of the VF | Re-download the variable file, not a single static style |
Audit checklist for self-hosting
Run through these against the report before writing @font-face. Each maps to a field the mapper returns.
| Check | Report field | Pass condition |
|---|---|---|
| Is it actually variable? | axis_count | Greater than 0 (no error thrown) |
| Are all expected axes present? | axes[].tag | Every tag your design needs appears |
| Do the ranges cover my values? | axes[].min/max | All CSS values fall inside each range |
| Do my styles exist as instances? | instances[].name | Each required style is listed |
| Do instance coords match my CSS? | instances[].coordinates | Coordinates equal what your CSS sets |
Cookbook
Auditing self-hosted variable fonts and turning the result into correct CSS. Use the values your audited file reports.
Detect a single-axis slice from the API
ExampleYou expected Roboto Flex's many axes but the file audits to wght only. That means the download was a sliced request, not the full build.
expected: wght, wdth, opsz, GRAD, slnt, XTRA ...
audit: axis_count 1
axes = [ wght 100-1000 ]
Fix: re-download the full variable build (do not restrict axes in
the API request).Confirm a clamped weight range
ExampleYour hover effect sets wght 900, but the audited max is 700 — the file was clamped. Either re-download the full range or cap your CSS at 700.
audit: wght min 100, max 700
CSS: .x:hover { font-variation-settings: "wght" 900; } /* clamps to 700 */
Fix: re-download with full range OR change 900 -> 700Build self-hosted @font-face usage from an instance
ExampleUse the generated snippet for the instance you want, pointing your selector at the self-hosted file's family.
/* css_examples, instance "Medium" */
@font-face { font-family: "Inter"; src: url('/fonts/Inter.var.woff2') format('woff2'); }
.body { font-family: "Inter"; font-variation-settings: "wght" 500; }Verify a downloaded ZIP face is the variable one
ExampleGoogle Fonts ZIPs often include static styles plus the variable file. Audit each to find the one with axes; the statics will report no variable axes.
Inter-Regular.ttf -> error: no variable axes (static) Inter[wght].ttf -> axis_count 1, wght 100-900 (the VF) Self-host Inter[wght].ttf, not the static.
Commit the audit next to the self-hosted font
ExampleSave the JSON in your repo so refreshing the font later is a diff, not a surprise. A changed axis count or range shows up immediately.
public/fonts/Inter.var.woff2 public/fonts/Inter.var.axes.json (from this tool) axis_count: 1, wght 100-900, instances: Thin..Black
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.
Downloaded a static style instead of the VF
Error — no variable axesGoogle Fonts download ZIPs include static weights alongside the variable file. Auditing a static face throws "This font has no variable axes (no fvar table or empty axis list)." Find the file with [wght]-style axis tags in its name and audit that one.
API returned a single-axis slice
Audit mismatch — fewer axesA request that restricts axes (or a tool that slices) yields a file with fewer axes than the family advertises. The audit shows axis_count smaller than expected. Re-download the full variable build to get every axis your design needs.
Weight range is clamped versus the preview
Audit mismatch — narrowed rangeThe served file's wght max can be lower than the family's full range if the request clamped it. CSS values above the audited max clamp silently. Re-download the full range, or cap your CSS at the audited max.
WOFF2 self-host file decompresses and audits cleanly
Works — exact file auditedWOFF2 is Brotli-decompressed in-browser before the fvar table is read, so you audit the precise web file you serve rather than a converted copy. This is the recommended thing to audit for self-hosting.
Axis names show as tags after download
Cosmetic — name fallbackIf the downloaded build's name records aren't Windows-English, axis names display as their 4-character tags. This does not affect CSS, since you write tags in font-variation-settings regardless. It is purely a labelling artefact.
Named instances missing from a subset build
Expected — instances droppedSome subsetting or slicing pipelines drop named-instance records while keeping the axes. The audit then shows axes but an empty css_examples list. Build CSS from the axis ranges, or re-download an unsubsetted build to recover the instances.
Self-hosted file exceeds the Free size cap
Error — over limitA large CJK variable family can exceed the 5 MB Free per-job cap. The tool reports the exact limit. Most Latin variable WOFF2 files are well under it; upgrade the tier (50 MB Pro, 1 GB Developer) for big families.
Auditing a TrueType Collection from a foundry bundle
Error — unsupported formatA .ttc bundle isn't supported; you get "Unsupported font format: ttc." Extract the single variable face you intend to self-host, then audit it. The Font Format Identifier confirms the container type.
Fractional values in the audited ranges
Expected — Fixed 16.16Ranges decode from Fixed 16.16, so widths or parametric defaults may be fractional (e.g. wdth 62.5). Use the exact values in your self-hosted CSS; rounding can land on a slightly different design point than the foundry intended.
Frequently asked questions
Why doesn't my self-hosted variable font respond to font-weight changes?
Audit the file. If the wght axis is missing or its range is narrower than your CSS values, the weights clamp or do nothing. Often the downloaded file is a static style or a sliced build rather than the full variable font.
How do I know if I downloaded the real variable build?
Drop the file here. If it reports an fvar with the axes you expect, it is the variable build. If it errors with 'no variable axes', you grabbed a static style. If it has fewer axes than the family lists, you got a slice.
Can I audit the WOFF2 I actually serve?
Yes — that is the recommended thing to audit. WOFF2 is decompressed in your browser before the fvar table is read, so you inspect the exact production file rather than a converted copy.
Why does my Google Fonts file have fewer axes than the website shows?
The Google Fonts API can return an axis-restricted or single-axis slice depending on the request. The website preview shows the full family. Re-download the full variable build if you need every axis.
A Google Fonts ZIP has several files — which do I self-host?
The variable file (its name usually contains an axis tag like [wght]); the others are static weights. Audit each: the statics report 'no variable axes', the variable one reports the fvar axes. Self-host the variable one.
Do the axis tags vs names matter for self-hosting?
Only tags matter for CSS — you write font-variation-settings with tags. If names display as tags in the audit (because name records aren't Windows-English), that is cosmetic and does not affect your self-hosted CSS.
Why is css_examples empty for my downloaded font?
Some subset/slice pipelines drop named-instance records while keeping the axes. With no instances there is nothing to template, so css_examples is empty. Build CSS from the audited axis ranges, or re-download a full build.
Is my downloaded font uploaded anywhere when I audit it?
No. The fvar table is parsed in your browser from the file's bytes. Nothing leaves the page, so you can safely audit foundry or licensed fonts before bundling them.
What if the file is too big to audit on the free tier?
The per-job cap is 5 MB on Free, 50 MB on Pro, and 1 GB on Developer. Most Latin variable WOFF2 files are well under 5 MB; large CJK families may need a higher tier.
Does this generate the @font-face for me?
It generates font-variation-settings blocks per instance plus the axis ranges. For complete @font-face declarations, use the Font Face Generator; to build CSS straight from a Google Fonts URL, see the Google Fonts CSS Generator.
Can it convert or optimise the font?
No, it is read-only. To bake one instance into a static file, use the Variable Font Freezer; to identify or convert formats, use the Font Format Identifier. This tool only audits the design space.
Should I commit the audit JSON to my repo?
Yes — it is a small JSON record of the font's axes and instances. Keeping it next to the self-hosted file lets you diff after any font refresh and catch a changed build before it reaches production.
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.