How to expose variable font weight instances without a python toolchain
- Step 1Decide: map (CSS) or bake (binary)? — If you want addressable weights from one file and don't need a smaller font, map with this tool. If you need to trim the axis range or emit true static instances (e.g. for an engine without variable support), you need `fonttools varLib.instancer` — this CSS approach can't shrink the binary.
- Step 2Host your variable WOFF2 — Put the variable file under your own domain and note its URL. The builder writes that URL verbatim into the `src`; it never fetches or modifies the file.
- Step 3Enter family name and URL — Two fields only — family (default `Inter`) and font URL (default `/fonts/inter.var.woff2`). No Python environment, no CLI flags, no instancer arguments.
- Step 4Generate the nine @font-face blocks — Out comes a header comment plus Thin 100 → Black 900, each with `font-weight === wght`, `font-style: normal`, `font-display: swap`. The ladder and axis are fixed; there's nothing to configure.
- Step 5Commit the CSS — Save the generated `<family>-weight-presets.css` into your repo. It's plain text — diffable, reviewable, and reversible, unlike a re-instanced binary.
- Step 6Reach for fonttools only if you must shrink or statically instance — If profiling shows the full variable file is too heavy, that's a binary problem CSS can't solve — use `varLib.instancer` to trim axes or [variable-font-freezer](/font-tools/variable-font-freezer) to bake a static weight. CSS mapping and binary baking are complementary, not competing.
CSS weight mapping vs binary instancing
What this browser-only tool does versus what a Python toolchain does. They solve different problems.
| Concern | This tool (CSS @font-face) | fonttools varLib.instancer |
|---|---|---|
| Toolchain needed | None — runs in the browser | Python + fonttools install |
| Touches the font binary? | No | Yes — rewrites tables |
| Shrinks the file? | No — full variable file stays | Yes — can drop axes / instance |
| Addressable weights | Yes — 9 named via @font-face | Yes — true static instances |
| Reversible | Yes — delete the CSS | No — re-run from source |
| Risk of corrupting font | None (doesn't write binary) | Possible with wrong args |
What the generated CSS contains
The deterministic structure emitted with no configuration beyond the two text fields.
| Element | Value | Editable? |
|---|---|---|
| Number of @font-face blocks | 9 | No — fixed ladder |
| Weights | 100, 200, 300, 400, 500, 600, 700, 800, 900 | No |
| Axis written | wght only | No |
| font-style | normal | No |
| font-display | swap | No |
| src format hints | woff2-variations, then woff2 | No |
When CSS mapping is and isn't enough
Pick the right approach for the outcome you need.
| Goal | Use this CSS tool? | Otherwise |
|---|---|---|
Write font-weight: 600 against a variable font | Yes | — |
| Reduce variable file size | No | fonttools axis trim |
| Ship statics for a non-variable engine | No | variable-font-freezer |
| Inspect the real axis range first | No | font-metadata-extractor |
Cookbook
Browser-generated CSS that replaces a fonttools step for the common 'make my weights addressable' case.
One generated block (no fonttools involved)
ExampleThis is produced entirely client-side from two text inputs.
@font-face {
font-family: "Inter";
src: url("/fonts/inter.var.woff2") format("woff2-variations"),
url("/fonts/inter.var.woff2") format("woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
font-variation-settings: "wght" 600;
}The fonttools command you're NOT running
ExampleFor contrast: this is what binary instancing would look like. The CSS approach avoids it entirely when you only need addressable weights.
# Binary instancing (NOT needed for CSS weight mapping): pip install fonttools fonttools varLib.instancer Inter.var.ttf wght=600 \ -o Inter-SemiBold.ttf # → produces a static file; repeat per weight, then re-compress to WOFF2
Committing the generated CSS to your repo
ExamplePlain-text artifact, reviewable in a PR — unlike a re-instanced binary blob.
# git diff shows readable changes:
+ @font-face { font-family:"Inter"; font-weight:700;
+ font-variation-settings:"wght" 700; ... }
# vs a binary instance: Binary files differUsing the mapped weights
ExampleComponent CSS stays toolchain-agnostic plain font-weight.
.btn { font-family: "Inter"; font-weight: 600; }
.btn--bold { font-weight: 700; }When you do still need the binary tool
ExampleIf size is the problem, CSS can't help — trim the axis with fonttools, then regenerate presets only for the weights you kept.
# Trim to the weights you actually use, THEN map the survivors: fonttools varLib.instancer Inter.var.ttf wght=300:700 -o Inter-trim.ttf # re-compress to WOFF2, host, and generate presets for 300..700 only
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.
You expected the file to get smaller
No size changeCSS weight mapping never touches the binary, so the full variable WOFF2 stays exactly as large as it was. If shrinking is your goal, this is the wrong tool — that's a job for axis trimming via fonttools varLib.instancer or baking a static with variable-font-freezer. The CSS approach trades zero size savings for zero toolchain.
Target engine doesn't support variable fonts
Variation ignoredA renderer without OpenType variations support ignores font-variation-settings and shows one fixed weight regardless of the preset. CSS mapping can't fake variability on such engines — you need actual static instances (binary instancing). For the browser web platform this is a non-issue; every supported browser since 2018 handles variable fonts.
Axis range narrower than 100–900
Extremes clampBecause the tool never reads the font, it always emits 100–900. If the real wght axis is narrower, the browser clamps out-of-range presets to the nearest end, so you get duplicate-looking weights. Inspect the true range with font-metadata-extractor and delete the blocks outside it.
You wanted custom instance names from the font's STAT table
Not readSome fonts define named instances (e.g. 'SemiBold', 'Condensed Bold') in their fvar/STAT tables. This tool does not read them — it emits its own fixed nine-name ladder. If you need the font's own instance names or non-weight axes, that requires reading the binary (analysis tools) or instancing it.
Multi-axis font (weight + width + optical size)
Only wght handledThe builder writes only the wght axis. For a font with wdth, opsz, or slnt, those axes stay at their defaults in every preset. To address other axes you must add font-variation-settings entries yourself, or instance them in the binary.
Bad font URL
404The URL string is written verbatim and never validated. A path that's wrong, relative to the wrong base, or pointing at localhost ships broken CSS that fails silently to the fallback font. Paste the deployed path.
You're in a build pipeline and want this automated
Manual / browser toolThis is an interactive browser generator. The output, though, is just static CSS you can commit — for most teams that's fine to paste once. If you genuinely need programmatic generation per build, the CSS pattern is simple enough to template in your own script; the binary instancing path (fonttools) is the one that truly belongs in a pipeline.
Worried browser-side CSS will corrupt the font
Cannot happenUnlike binary instancing, CSS mapping cannot corrupt the font — it only emits text that references the file. The worst failure mode is a wrong URL or a clamped weight, both fixable by editing the CSS. There's no binary write to go wrong.
Frequently asked questions
Do I need Python or fonttools to use this?
No. This builder runs entirely in the browser and emits CSS from two text fields. There's no pip install, no varLib.instancer, no build step. fonttools is only needed if you want to shrink the file or produce true static instances — which CSS mapping deliberately doesn't do.
What's the difference between CSS weight mapping and binary instancing?
CSS mapping (this tool) writes @font-face blocks that bind font-weight to font-variation-settings: "wght" against the unchanged variable file. Binary instancing (fonttools varLib.instancer) rewrites the font, optionally trimming axes and emitting smaller static files. Mapping is reversible and toolchain-free; instancing shrinks the binary but is destructive and needs Python.
Will this make my font file smaller?
No. The full variable WOFF2 stays on the wire — CSS mapping never touches the binary. If size is the problem, trim axes with fonttools or bake a single static weight with variable-font-freezer.
Can I get the font's own named instances?
Not from this tool. It emits its own fixed nine-name ladder (Thin–Black) and never reads the font's fvar/STAT named instances. Reading those requires parsing the binary — use an analysis tool for that.
Does it handle width or optical-size axes?
No — only the wght axis is written. Other axes (wdth, opsz, slnt) stay at their defaults. Add them yourself via font-variation-settings, or instance them in the binary if you need static variants.
Is the output safe to commit to source control?
Yes — it's deterministic plain CSS, so it diffs cleanly in a PR. That's an advantage over a re-instanced binary, which shows up as an opaque 'Binary files differ' change.
Could browser-side mapping corrupt my font?
No. The tool only writes text that references your file; it performs no binary write. The only things that can go wrong are a wrong URL or a weight clamped because your axis is narrower than 100–900 — both are fixable by editing the CSS.
When should I still reach for fonttools?
When you need to reduce the file size (axis trimming), ship true static instances for an engine without variable support, or apply non-weight axis changes to the binary. Those are binary operations CSS mapping can't perform.
Does it work for engines without variable font support?
No — such engines ignore font-variation-settings and render one fixed weight. CSS mapping assumes a variable-aware renderer (every modern browser since 2018). For non-variable engines you need actual static instances.
Can I automate this in CI?
It's an interactive browser tool, so it's not a CLI. But the CSS pattern it emits is simple and stable enough to template in your own build script if you must. For pipeline work that changes the binary, fonttools is the right CI citizen.
Why nine weights and not the exact ones I use?
The ladder is hardcoded to the nine round hundreds. After generating, simply delete the blocks for weights you don't ship — or, if you've trimmed the binary, generate and keep only the in-range ones. The tool gives you the full set; pruning is a one-line edit each.
Is the output free and limited by file size?
It's a free generative tool with no font upload, so the file-size tiers that gate binary-parsing tools (5 MB free / 50 MB pro) don't apply — there's no binary to size-check. You get the same deterministic CSS regardless of how large the variable font at the URL happens to be, since the tool never touches it.
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.