FontSelf Blog
Variable Fonts Explained: One File Instead of Six (and How to Self-Host Them)
For most of the web's history, if you wanted to use a typeface at multiple weights, you needed a separate font file for each one. Inter Regular was one file. Inter Bold was another. Inter Light, Inter Medium, Inter SemiBold — each a separate binary, each a separate HTTP request, each adding to your page weight. A typical font setup with four or five weights meant four or five files to download before your text rendered correctly.
Variable fonts change this entirely. One file. Every weight. Every width. Every style variation — all encoded in a single WOFF2 binary that your browser interpolates at render time. This is not a minor optimization. For a typical multi-weight font setup, switching to the variable version cuts your font-related HTTP requests from five or six down to one.
How Variable Fonts Actually Work
A traditional font file is a snapshot — it encodes the exact shapes of every glyph at one specific weight and style. Inter Regular encodes every letter, number, and symbol at weight 400, and that is all it contains.
A variable font file encodes something different: the rules for how glyphs change across a range. Instead of storing the shapes at weight 400 and the shapes at weight 700 as separate files, a variable font stores the shapes at the extremes of the range and the mathematical description of how to interpolate between them. The browser handles the interpolation at render time.
This is done through axes — named dimensions of variation. The most common axes are:
wght— Weight. The range100 900covers everything from Thin to Black. You can use any value in between, including non-round numbers like453if you want.ital— Italic. A binary or continuous axis depending on the font. Some fonts encode italic as a separate axis rather than a separate file.opsz— Optical size. Adjusts glyph details for the size at which the font is being displayed. A font withopszsupport will automatically render slightly differently at 12px versus 72px for better legibility at each size.wdth— Width. Controls how condensed or expanded the letterforms are.slnt— Slant. A continuous slant axis, distinct from italic, used in some typefaces.
Not every variable font supports all axes. Inter supports wght and opsz. Roboto Flex supports eight axes. You work with whatever axes the font designer chose to encode.
The @font-face Difference
This is where self-hosting variable fonts diverges from static fonts. The CSS syntax is different in two important ways.
For a static font, you write one @font-face block per weight:
@font-face {
font-family: 'Inter';
font-weight: 400;
font-style: normal;
font-display: swap;
src: url('./fonts/inter-400.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-weight: 700;
font-style: normal;
font-display: swap;
src: url('./fonts/inter-700.woff2') format('woff2');
}For a variable font, you write a single @font-face block that declares the full weight range and points at the single variable file:
@font-face {
font-family: 'Inter';
font-weight: 100 900;
font-style: normal;
font-display: swap;
src: url('./fonts/inter-variable.woff2') format('woff2-variations');
}Two things to notice. First, font-weight: 100 900 is a range — two values separated by a space, not a comma. This syntax tells the browser that this single font file covers the entire weight range from 100 to 900. Second, the format hint is woff2-variations, not woff2. This is how you signal to the browser that the file is a variable font. Modern browsers understand both, but the explicit hint helps with correct fallback behavior in older environments.
Once this @font-face block is in place, you can use any weight value in your CSS:
h1 { font-weight: 800; }
p { font-weight: 400; }
.caption { font-weight: 350; }The browser renders each at exactly the specified weight, interpolating from the single file. No additional files needed.
Variable Fonts and Optical Size
The opsz axis deserves special mention because it works differently from wght. While you typically set weight manually with font-weight, optical size can be set to respond automatically to the font's rendered size using the font-optical-sizing CSS property.
body {
font-optical-sizing: auto;
}With font-optical-sizing: auto, the browser automatically maps the font's CSS font-size to the opsz axis. A heading rendered at 48px will automatically use the optical size tuning designed for large display text. Body copy at 16px will use the tuning optimized for small reading sizes. You get typographically correct rendering at every size with no extra effort.
Inter supports this axis. If you are self-hosting Inter as a variable font, font-optical-sizing: auto is worth adding to your base styles.
File Size: Is Variable Actually Smaller?
Variable font files are larger than any single static weight file. An Inter Regular WOFF2 might be 90KB for the latin subset. The Inter variable WOFF2 for the same subset is around 260KB.
However, the comparison that matters is not one file against one file — it is one file against the set of static files you would otherwise load. If you were loading Inter at 300, 400, 600, and 700 weights, you were downloading four files totaling roughly 360KB. The single variable file at 260KB is smaller than that set, delivers every weight in between, and costs one HTTP request instead of four.
The crossover point is roughly two static weights. If you are only using one weight, the static file is smaller and there is no reason to use the variable version. If you are using two or more weights, the variable file wins on total bytes transferred and wins significantly on request count.
How FontSelf Handles Variable Fonts
When you select a font in FontSelf and it supports variable axes, the weight selector is replaced with axis range inputs rather than individual weight checkboxes. You set the minimum and maximum for each axis — or leave them at the font's defaults — and FontSelf fetches the single variable WOFF2 file and generates the correct @font-face block with the range syntax and woff2-variations format hint.
The ZIP you download contains one WOFF2 file and a fonts.css with the ready-to-paste declaration. There is nothing manual to write and no risk of using the static syntax by mistake on a variable font.
FontSelf detects variable fonts automatically from the Google Fonts API metadata. If the font has an axes field in the catalog, it is treated as variable. If it does not, the standard per-weight checkbox interface is shown instead.
Browser Support
Variable font support is excellent. All modern browsers have supported variable fonts since 2018. Chrome, Firefox, Safari, and Edge all handle the woff2-variations format and the range font-weight syntax without issues.
If you need to support Internet Explorer 11, variable fonts do not work there at all — IE11 has no variable font support. The practical approach for IE11 is to add a separate static @font-faceblock as a fallback inside a conditional comment or feature query, pointing at a static WOFF2 at your most common weight. IE11's global usage is below 0.5% as of 2025 and declining, so for most projects this is not worth the additional complexity.
For all other browsers, deploy the variable font without a static fallback. The feature support is broad enough that conditional loading adds complexity with no benefit.
The Self-Hosting Checklist for Variable Fonts
Once you have your ZIP from FontSelf:
1. Place the single WOFF2 file in your /public/fonts directory
One file. Name it something clear like inter-variable.woff2 so it is obvious at a glance that it is the variable version, not a static weight.
2. Add the generated @font-face block to your global stylesheet
The fonts.css from the ZIP has the correct range syntax and format hint already written. Paste it at the top of your stylesheet or import it as a separate file.
3. Add a preload link for the variable font file
<link rel="preload" href="/fonts/inter-variable.woff2" as="font" type="font/woff2" crossorigin>
One preload link for one file. This is simpler than static fonts where you might need to decide which weights to preload.
4. Set Cache-Control headers
Cache-Control: max-age=31536000, immutable
Variable font files are as static as any other font binary. Cache them aggressively.
5. Optionally add font-optical-sizing: auto to your base styles
If the font supports the opsz axis — Inter does — this gives you automatic optical size optimization at every font size with no additional configuration.
6. Remove the Google Fonts CDN link
Delete any <link> tags or @import rules pointing at fonts.googleapis.com. Verify in DevTools Network tab, filtering by Font, that no requests go to fonts.gstatic.com.
Summary
Variable fonts replace multiple static weight files with a single WOFF2 binary that the browser renders at any weight within the encoded range. The @font-face syntax uses a two-value weight range and the woff2-variations format hint. For any project using two or more font weights, switching to the variable version reduces both total bytes transferred and HTTP request count. Browser support has been universal since 2018. FontSelf handles the detection, download, and CSS generation automatically — the ZIP it produces contains exactly one file and one ready-to-paste stylesheet.