Variable Fonts vs Static Weights

Variable fonts put an entire family's weight range — and sometimes width, slant and optical size — into one file. The usual advice is that they are more efficient than loading several static weights, and for many sites that is true. It is not true for every site: a variable font is bigger than any single weight, and a page that only needs regular and bold may download more with a variable font than with two static files. The right choice depends on how many weights each page uses, how aggressively the font can be subset, and which axes you really need.

This guide measures both options with Inter and Source Serif on a static documentation and blog site, shows how to subset and limit axes with fonttools, and gives a simple rule for choosing per family. It is part of Font Loading Strategies for Static Sites.

Prerequisites

  • The font families in use, with licences that allow subsetting and self-hosting.
  • Python with fonttools and brotli (pip install fonttools brotli).
  • A per-template list of the weights actually used, from an audit such as Auditing Font Payload per Page.

What Each Option Costs

Inter, subset to Latin (basic Latin, Latin-1 supplement, common punctuation), WOFF2:

OptionFilesBytes
Static 400123 KB
Static 400 + 700246 KB
Static 400 + 500 + 600 + 700493 KB
Variable, full weight axis 100–900171 KB
Variable, weight axis limited to 400–700149 KB
Variable 400–700, Latin subset without extended punctuation144 KB

The crossover is clear: with two weights, static files (46 KB) and the limited variable font (44–49 KB) are about equal; with four weights, the limited variable font is half the size of the static set, in one request.

WOFF2 bytes for static and variable Inter options Vertical bars of WOFF2 bytes. One static weight 23 kilobytes. Two static weights 46 kilobytes. Four static weights 93 kilobytes. Full variable font 71 kilobytes. Variable font limited to weights 400 to 700, 49 kilobytes. Inter, Latin subset, by option 23 KB 1 static weight 46 KB 2 static weights 93 KB 4 static weights 71 KB Variable, 100-900 49 KB Variable, 400-700
Built with pyftsubset and fonttools instancer from Inter 4.0; sizes after WOFF2 compression.

Step 1: Subset the Variable Font

pyftsubset removes glyphs you do not need. Variable fonts subset the same way as static ones:

pyftsubset InterVariable.ttf \
  --unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215" \
  --layout-features="kern,liga,calt,tnum,case" \
  --flavor=woff2 --output-file=inter-var-latin.woff2

Keeping only the OpenType features you use (tnum for tables, case for uppercase punctuation) saves a few kilobytes more. The detailed process, including checking which characters content actually uses, is in Subsetting Variable Fonts for Faster First Render.

Step 2: Limit the Axes

Most sites use a narrow slice of the weight axis. fonttools varLib.instancer can pin or restrict axes, keeping only the part of the design space you need:

fonttools varLib.instancer inter-var-latin.woff2 wght=400:700 opsz=drop -o inter-400-700.woff2

Restricting weight from 100–900 to 400–700 cut the file from 71 KB to 49 KB. Dropping the optical size axis (opsz), which Inter 4 includes, saved another 5 KB; keep it if headings use optical sizing through font-optical-sizing: auto. Declare the restricted range in @font-face so the browser does not request weights outside it from this file:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-400-700.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
Variable font processing pipeline The vendor variable font of about 340 kilobytes as TTF is subset to Latin characters and needed features, becoming 71 kilobytes as WOFF2. The weight axis is limited to 400 to 700 and optical size is dropped, giving 44 kilobytes. The font-face rule declares weight 400 to 700 and the file is preloaded on templates that use it above the fold. From the vendor file to what ships Vendor file InterVariable. ttf, 340 KB Subset Latin + features, 71 KB Limit axes wght 400-700, 44 KB @font-face font-weight: 400 700 Ship one file per style
The same two commands run in the build whenever the vendor font is updated, so the shipped file stays reproducible.

Step 3: Decide per Family

Applying the numbers to each family on the site:

  • Inter (UI and body). Pages use 400, 500, 600 and 700. Variable limited to 400–700: one 44 KB file instead of four static files totalling 93 KB.
  • Source Serif (blog body). Blog posts use 400 and 400 italic, and 600 for headings. Variable upright plus variable italic would be two files of about 60 KB each; static 400, 600 and 400 italic came to 3 files totalling 71 KB. The static set won, and the italic loads without preload because it is rarely above the fold.
  • JetBrains Mono (code). Only 400 is used. Static 400 at 19 KB; the variable font limited to a single weight is pointless.

The rule the team adopted: use a subset, axis-limited variable font when a template needs three or more weights of a family; use static files for one or two weights.

Decision rule for variable fonts versus static weights The root question asks how many weights of the family a template uses. One weight means a single static file. Two weights means comparing two static files with an axis-limited variable file, usually about equal, so prefer static for simpler caching. Three or more weights means an axis-limited, subset variable font, usually half the bytes in one request. Variable or static for this family? How many weights does a template use? One One static file e.g. JetBrains Mono 400, 19 KB Two About equal compare; static is simpler Three or more Limited variable font Inter 400-700, 44 KB
Italic counts as a separate family for this rule, because variable italics usually ship as a separate file.

Rendering and Design Benefits

Beyond bytes, variable fonts let designers use intermediate weights — 450 for body text on dark backgrounds, where light-on-dark text looks thinner, or 550 for navigation — without another download. The site uses font-weight: 450 for body text in dark mode, a small legibility improvement that would have cost a whole extra file with static fonts. Weight transitions on hover animate smoothly, though that is rarely worth doing.

Rendering quality is the same as static fonts in current Chromium, Firefox and Safari. Older Safari versions (before 15) had occasional hinting differences at small sizes on non-Retina displays; for a site whose analytics showed under 0.3% of such readers, it was not a concern.

Caching and Updates

One file per family also changes caching behaviour. With static weights, a reader who lands on a page using only 400 downloads one file, and a later page that adds 700 downloads another; the cost is spread across pages. With a variable font, the first page pays for all weights at once, and every later page is free. For documentation sites with long sessions that trade is favourable; for landing pages where most visitors see one page, the static option can be cheaper for that single view, which is one more reason to decide per template rather than per site.

Font updates are rarer than code changes but they happen: vendors fix glyphs, add languages and adjust metrics. Keep the vendor's original file in the repository and run the subset and instancer commands in the build, writing a content-hashed output file. A vendor update is then a one-file change that regenerates the shipped font, busts the cache through the new hash, and can be reviewed with a before-and-after screenshot of the typography test page. Hand-edited subset files, by contrast, tend to be regenerated with slightly different settings each time and drift in size and coverage.

Finally, check metric compatibility when switching an existing site from static weights to the variable version of the same family. Most families keep identical vertical metrics, but some variable releases changed line gap or ascender values, which alters line heights across the whole site; comparing a screenshot of a long article before and after catches it immediately.

Measured Impact

MeasureAll static weightsMixed (variable Inter, static serif and mono)
Font bytes, docs template112 KB (5 files)63 KB (2 files)
Font bytes, blog template164 KB (7 files)115 KB (4 files)
Font requests before LCP, docs32
LCP p75 mobile, docs (field)1.78 s1.66 s
Weights available to designers (Inter)4 fixedany from 400 to 700

Pitfalls & Rollback

  • Shipping the full design space. Unrestricted axes can double the file; limit them.
  • Forgetting the weight range in @font-face. Without font-weight: 400 700, browsers may synthesize or mis-select weights.
  • Assuming variable is always smaller. For one or two weights, measure.
  • Separate italic files. Variable italics are usually a second file; count them as a separate family.
  • Rollback: keep the static files alongside; swapping the @font-face rules back is a one-commit change.

Conclusion

Variable fonts are not automatically lighter; they win when a template uses three or more weights, and only after subsetting and limiting axes to the range actually used. For one or two weights, static files are as small and simpler. Apply the rule per family and per template, and the site gets fewer bytes, fewer requests and more typographic freedom where it matters.

FAQ

Is a variable font always smaller than static weights?

No. A variable font contains the outlines and deltas for its whole design space, so it is larger than any single static weight. It becomes smaller than the equivalent set of static files only once a page uses roughly three or more weights, depending on the family.

Can I reduce a variable font's size?

Yes. Subset it to the characters you need, and limit its axes to the range you use with fonttools instancer, for example restricting weight to 400 to 700 instead of 100 to 900. Both reduce the file considerably.

Do variable fonts render differently?

Rendering quality in current browsers is equivalent to static fonts. Variable fonts also allow intermediate weights such as 450 or 550, optical size adjustments and smooth weight transitions in CSS, which static fonts cannot do.

Which is better for LCP?

Whichever gets the text above the fold its final font in fewer bytes and requests. When the first view uses one or two weights, one or two small static files often win. When it uses three or more, one subset variable file usually wins.

Filed underComparisons