How to generate nine @font-face weight presets for a variable font
- Step 1Have your variable WOFF2 hosted (or know its final URL) — The builder writes a `src: url("...")` pointing at whatever path you give it. Self-host the variable file under your own domain and note its path — e.g. `/fonts/inter.var.woff2`. If you only have a static font, this tool is the wrong fit; freeze or build a static set with [variable-font-freezer](/font-tools/variable-font-freezer) instead.
- Step 2Enter the family name — Type the CSS family you want, e.g. `Inter`. This becomes the `font-family` quoted in every `@font-face` block and the kebab-case stem of the download filename (`inter-weight-presets.css`). It is a label only — it does not have to match the name table inside the font.
- Step 3Enter the font URL — Paste the path or URL to the variable WOFF2 (default `/fonts/inter.var.woff2`). The exact string is dropped into both `src` entries verbatim — the tool does not resolve, fetch, or validate it. A typo here ships broken CSS, so paste the real deployed path.
- Step 4Generate the CSS — The tool emits a commented header plus nine `@font-face` blocks (Thin 100 → Black 900). There are no other options — the weight ladder is fixed, the axis is always `wght`, the style is always `normal`, and `font-display` is always `swap`.
- Step 5Drop the CSS into your stylesheet — Paste the block (or import the downloaded `<family>-weight-presets.css`) ahead of any rule that references the family. From then on `font-weight: 300` resolves to `wght 300`, `font-weight: 700` to `wght 700`, and so on, all from the single cached file.
- Step 6Use plain font-weight in your component CSS — Write `.headline { font-family: "Inter"; font-weight: 800; }` — no `font-variation-settings` needed in your component layer because each preset already carries it. For non-100-multiple values (e.g. `wght 350`) you must add `font-variation-settings` yourself; the presets only cover the nine round numbers.
The nine presets the builder emits (fixed ladder)
This list is hardcoded in the handler. Every preset sets font-weight === wght, font-style: normal, and font-display: swap. The form has no field to add, remove, or rename a preset.
| Preset name | font-weight | font-variation-settings "wght" | font-style |
|---|---|---|---|
| Thin | 100 | 100 | normal |
| Extra Light | 200 | 200 | normal |
| Light | 300 | 300 | normal |
| Regular | 400 | 400 | normal |
| Medium | 500 | 500 | normal |
| Semi Bold | 600 | 600 | normal |
| Bold | 700 | 700 | normal |
| Extra Bold | 800 | 800 | normal |
| Black | 900 | 900 | normal |
What each form field controls
The entire interface is two text inputs. There is no upload, no axis-range field, no style toggle, and no preset editor.
| Field | Default | Where it lands in the output |
|---|---|---|
| Family name | Inter | Quoted font-family in all 9 blocks; kebab-cased into the filename <family>-weight-presets.css |
| Font URL | /fonts/inter.var.woff2 | Inserted verbatim into both src url() entries of every block |
| (weight ladder) | fixed Thin–Black | Not editable — always the nine 100-step values |
| (axis) | wght | Not editable — only the weight axis is written |
| (font-display) | swap | Not editable — always swap |
The src line and why the order matters
Every block repeats the same two-entry src list pointing at the same URL with two different format hints.
| src entry | format hint | Purpose |
|---|---|---|
url("<path>") | woff2-variations | Tells variation-aware browsers this WOFF2 carries axes — listed first so capable engines pick it |
url("<path>") | woff2 | Plain WOFF2 fallback hint for engines that don't recognise woff2-variations; same bytes, same URL |
Cookbook
The exact CSS the builder produces, plus how to consume it. The output is deterministic — same two inputs always yield the same nine blocks.
Default output for Inter
ExampleWith the default family and URL, the header comment plus the first and last blocks look like this. The middle seven follow the identical shape with their own weight.
/* Variable font weight presets for Inter
* One source file (/fonts/inter.var.woff2) shared across nine discrete weight bindings.
* Browsers cache the WOFF2 once; each font-weight number maps to a
* font-variation-settings axis value via @font-face.
*/
@font-face {
font-family: "Inter";
src: url("/fonts/inter.var.woff2") format("woff2-variations"),
url("/fonts/inter.var.woff2") format("woff2");
font-weight: 100;
font-style: normal;
font-display: swap;
font-variation-settings: "wght" 100;
}
/* ... Extra Light 200 through Extra Bold 800 ... */
@font-face {
font-family: "Inter";
src: url("/fonts/inter.var.woff2") format("woff2-variations"),
url("/fonts/inter.var.woff2") format("woff2");
font-weight: 900;
font-style: normal;
font-display: swap;
font-variation-settings: "wght" 900;
}Consuming the presets in component CSS
ExampleOnce the generated block is in your stylesheet, your component layer is ordinary font-weight — no font-variation-settings needed.
.caption { font-family: "Inter"; font-weight: 300; }
.body { font-family: "Inter"; font-weight: 400; }
.lead { font-family: "Inter"; font-weight: 500; }
.headline { font-family: "Inter"; font-weight: 800; }Custom family and self-hosted path
ExampleChange both fields and the same nine blocks regenerate with your strings substituted verbatim.
Family name: Recursive
Font URL: /assets/fonts/recursive.var.woff2
→ filename: recursive-weight-presets.css
→ each block: font-family: "Recursive";
src: url("/assets/fonts/recursive.var.woff2") ...Pairing with a preload for the critical weight
ExampleThe builder only writes @font-face. To start the variable file downloading early, add a preload tag yourself (or build it with the preload tool).
<!-- in <head>, before the CSS -->
<link rel="preload" href="/fonts/inter.var.woff2"
as="font" type="font/woff2" crossorigin>
<!-- then the generated weight-presets CSS -->Adding an in-between weight the ladder doesn't cover
ExampleThe presets are round hundreds only. For a 350 or 650 weight, write your own font-variation-settings rule — the cached file already supports it.
/* 350 isn't in the generated ladder — add it manually */
.subtle {
font-family: "Inter";
font-variation-settings: "wght" 350;
}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.
Your font's wght axis doesn't reach 100 or 900
Clamped at render timeThe builder hardcodes presets from wght 100 to wght 900 and never reads your font, so it can't know the real axis range. If your variable font's wght only spans, say, 300–700, the browser clamps out-of-range font-variation-settings values to the nearest valid axis end. wght 100 and wght 200 will both render at the font's 300 minimum, and wght 800/900 at its 700 maximum — you get duplicate-looking weights, not an error. Check the real range with variable-font-freezer (it reports the fvar axes) before relying on the extremes.
The font isn't actually variable
No variation appliedIf the URL points at a static WOFF2, the font-variation-settings: "wght" N lines are simply ignored by the browser and every preset renders at the font's single built-in weight. The CSS still parses with no error. This builder generates text; it does not inspect the file, so it cannot warn you. Use the font-metadata-extractor to confirm the file carries an fvar table first.
Wrong or relative font URL
404 — font fails to loadWhatever you type in the Font URL field is dropped verbatim into both src entries. A typo, a path that's relative to the wrong directory, or a localhost path shipped to production all yield a failed font fetch and fallback text. The tool never resolves or fetches the URL, so a bad path looks identical to a good one in the generated CSS — paste the real deployed path.
You need italic or oblique weights
Not generatedEvery preset is hardcoded font-style: normal. There is no italic toggle and no ital/slnt axis output. If your variable font has an italic axis, you must write those @font-face blocks yourself (duplicate the set with font-style: italic and add "ital" 1 or "slnt" -10 to font-variation-settings).
You want a custom preset like 'SemiBold' at 590
Not configurableDespite some documentation suggesting custom presets, the live handler ignores any preset list and always emits the nine fixed 100-step values with font-weight === wght. To bind a non-standard value, add a font-variation-settings: "wght" 590 rule by hand on the specific selector that needs it — the same cached file serves it.
Family name contains spaces or odd characters
200 OK — name quoted, filename sluggedSpaces are fine in the font-family value because the output quotes it (font-family: "Helvetica Now";). For the download filename the name is lowercased and spaces become hyphens (helvetica-now-weight-presets.css). Non-filename-safe characters in the family name are left in the CSS string but may produce an unusual filename — rename the download if needed.
Both src entries point at the same file — is that a duplicate download?
Single download by designEach block lists the URL twice with two format hints (woff2-variations then woff2), but the browser only fetches one resource: it walks the src list and downloads the first entry whose format it understands, stopping there. Across all nine blocks it's still one cached file because every block references the identical URL.
You expected a downloadable font file
CSS onlyThis tool's output type is CSS text, not a font binary. It produces <family>-weight-presets.css and nothing else — it neither modifies nor emits a font. To actually bake a single weight into a static TTF, use variable-font-freezer.
Minifier reformats the font-variation-settings
200 OK — preservedStandard CSS minifiers keep font-variation-settings: "wght" 700 intact (the quoted axis tag and number are required syntax). The repeated src URL across nine blocks compresses extremely well under gzip/brotli because the only thing that changes per block is the weight integer.
Frequently asked questions
Does this tool read or modify my font file?
No. It takes two text inputs — a family name and a font URL — and never uploads, fetches, or parses a font. It can't validate that your wght axis spans 100–900, that the URL resolves, or that the file is even variable. It is a pure CSS string generator. For anything that inspects the binary (axis ranges, fvar instances, metrics) upload the font to an analysis tool like font-metadata-extractor.
Why are there exactly nine presets?
The handler hardcodes a ladder of nine round weights: Thin 100, Extra Light 200, Light 300, Regular 400, Medium 500, Semi Bold 600, Bold 700, Extra Bold 800, Black 900. Each sets font-weight equal to its wght axis value. The form exposes no way to add, remove, or rename them — if you need other values, edit the generated CSS or add font-variation-settings rules in your own stylesheet.
Can I set a custom weight like 350 or 650?
Not through this tool — the ladder is fixed to the nine 100-step values. But the underlying variable file supports any value in its range, so write a one-off rule: .x { font-family: "Inter"; font-variation-settings: "wght" 350; }. The same cached WOFF2 renders it; you don't need a new @font-face.
Why does the src list the same URL twice?
The two entries differ by format hint: format("woff2-variations") first so variation-aware browsers recognise the axes, then plain format("woff2") as a fallback hint for engines that don't know the -variations token. Same bytes, same URL — the browser downloads only the first entry it understands, so it's one cached file, not two requests.
Does it generate italic presets?
No. Every block is font-style: normal and the only axis written is wght. If your font has an italic (ital) or slant (slnt) axis, duplicate the generated set yourself with font-style: italic and the appropriate font-variation-settings axis value.
What if my variable font's weight range is narrower than 100–900?
The browser clamps any font-variation-settings value outside the font's real range to the nearest axis end. So presets below your font's minimum all render at that minimum, and presets above its maximum all render at the maximum — you get visually duplicate weights rather than an error. Confirm the real range before depending on Thin or Black.
Is font-display: swap always used?
Yes — every preset hardcodes font-display: swap, which renders fallback text immediately and swaps to the variable font when it loads. If you need a different strategy (block, fallback, optional), change it in the generated CSS or read font-display-strategy for the trade-offs.
Will all nine @font-face blocks bloat my CSS?
Negligibly. The nine blocks differ only by their weight integer and otherwise repeat the same text, so gzip/brotli compress them to a few hundred bytes. The real cost is the single variable WOFF2 download — which is shared across all nine and cached once.
Does this duplicate the font download per weight?
No. All nine blocks reference the identical URL, so the browser caches one copy of the variable file and reuses it for every weight. That's the whole point of presets over shipping nine static weight files.
What's the output filename?
The family name lowercased with spaces turned into hyphens, suffixed -weight-presets.css — e.g. Helvetica Now becomes helvetica-now-weight-presets.css. The CSS content uses the family name exactly as typed (quoted).
Do I still need a preload tag?
Often yes for the critical above-the-fold weight. This builder writes only @font-face rules; it doesn't emit a preload. Add <link rel="preload" as="font" crossorigin> yourself, or generate one with the font preload tool, so the variable file starts downloading alongside the HTML parse.
Is this tier-limited?
It's a free generative tool. Because it takes no font upload, the file-size tiers that gate the analysis and conversion tools (5 MB free / 50 MB pro) don't apply here — there's no binary to size-check.
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.