Before variable fonts, if you wanted Inter in Regular, Medium, SemiBold, and Bold, you downloaded four separate WOFF2 files. Add italics and that's eight files. Each file contains the same 2,500+ glyphs with slightly different outlines — an enormous amount of redundant data shipped over the network.

Variable fonts (OpenType 1.8, ratified in 2016) solve this by storing master designs at the extremes of each axis and interpolation data (deltas) to generate every point in between. One file, infinite styles. The browser calculates intermediate shapes mathematically instead of downloading pre-rendered versions.

The practical impact is significant. A typical font family with 9 weights in regular and italic totals 2-3 MB as individual WOFF2 files. The variable equivalent: 300-500 KB. That's not a theoretical number — it's what Inter, Roboto Flex, and Source Sans 3 actually deliver. This guide covers the axes system, CSS integration, file size math, design possibilities, and how to create variable fonts.

Variation Axes: The Core Concept

A variation axis defines one dimension of typographic variation. The OpenType spec defines five registered axes with standardized behavior, and fonts can also define custom axes for effects the spec doesn't cover.

Registered Axes

The five registered axes have four-character tags in lowercase:

AxisTagCSS PropertyTypical RangeExample
Weightwghtfont-weight100-900Thin (100) to Black (900)
Widthwdthfont-stretch75-125%Condensed (75%) to Expanded (125%)
Slantslntfont-style: oblique Xdeg-12 to 0Upright (0) to Oblique (-12)
Italicitalfont-style: italic0 or 1Binary switch, triggers alternate glyphs
Optical sizeopszfont-optical-sizing8-144Caption (8pt) to Display (144pt)

Weight is by far the most common axis. Width is the second most useful — a single font that ranges from Condensed to Expanded means your heading and body text can use the same font file with different widths.

Optical size is underused but powerful. At small sizes (8-12pt), fonts need wider spacing, thicker strokes, and open counters for legibility. At display sizes (72pt+), fonts can afford thinner strokes and tighter spacing. Optical sizing adjusts these automatically based on font-size.

Custom Axes

Custom axes use four-character tags in UPPERCASE to distinguish them from registered axes. Some notable examples:

  • GRAD (Grade) — Adjusts stroke weight without changing letter width or spacing. Used by Material Symbols for icon weight changes that don't reflow text
  • CASL (Casual) — Interpolates between a formal and casual style. Used by Recursive
  • CRSV (Cursive) — Controls cursive vs upright letterforms. Used by Recursive
  • FILL (Fill) — Toggles between outlined and filled icons. Used by Material Symbols
  • MONO (Monospace) — Interpolates between proportional and monospaced spacing. Used by Recursive

Custom axes are accessed exclusively through font-variation-settings — there are no dedicated CSS properties for them:

/* Recursive with casual, monospace styling */
.code {
  font-family: 'Recursive';
  font-variation-settings: "CASL" 1, "MONO" 1, "wght" 400;
}

Using Variable Fonts in CSS

Variable fonts require a slightly different @font-face declaration and offer two approaches for setting axis values in CSS.

@font-face for Variable Fonts

@font-face {
  font-family: 'Inter Variable';
  src: url('/fonts/InterVariable.woff2') format('woff2');
  font-weight: 100 900;  /* declares the supported range */
  font-stretch: 75% 125%;  /* if the font has a width axis */
  font-style: oblique 0deg 10deg;  /* if slant axis */
  font-display: swap;
}

The key difference from static @font-face: range values for font-weight, font-stretch, and font-style. Instead of font-weight: 400 (single value), you declare font-weight: 100 900 (the full range). This tells the browser this one file handles all weights from Thin to Black.

Without the range declaration, the browser treats the variable font as a regular font at its default weight and won't interpolate.

High-Level CSS vs font-variation-settings

Registered axes have dedicated CSS properties (the "high-level" approach):

/* High-level: browser-integrated, cascades properly */
h1 {
  font-weight: 720;
  font-stretch: 85%;
  font-style: oblique 8deg;
  font-optical-sizing: auto;
}

Custom axes and fine-grained control use font-variation-settings (the "low-level" approach):

/* Low-level: all axes in one declaration */
h1 {
  font-variation-settings: "wght" 720, "wdth" 85, "slnt" -8;
}

Prefer high-level CSS properties for registered axes. font-variation-settings has a critical problem: it doesn't cascade per-axis. If you set font-variation-settings: "wght" 700, "wdth" 85 on a parent and override weight on a child with font-variation-settings: "wght" 400, you lose the wdth value. It resets to default. High-level properties don't have this problem — font-weight and font-stretch cascade independently.

Using Arbitrary Weight Values

Static fonts snap to predefined stops: 100, 200, 300... 900. Variable fonts support any integer value in the declared range. This enables precise typographic control:

body { font-weight: 370; }  /* slightly lighter than Regular */
h1 { font-weight: 720; }    /* between Bold and ExtraBold */
.caption { font-weight: 340; }  /* custom for small text */

/* Responsive weight based on viewport */
h1 {
  font-weight: clamp(500, 4vw + 400, 800);
}

This works because the browser mathematically interpolates between the font's master designs. If the font has masters at weight 400 and 700, weight 550 is calculated as the midpoint. The interpolation is smooth and accurate — you won't see rendering artifacts at in-between values.

File Size: The Hard Numbers

The central claim of variable fonts is smaller total payload. Here are real measurements:

Static vs Variable: Measured Sizes

Font FamilyStatic Files (WOFF2)Variable File (WOFF2)Savings
Inter (9 weights)1.19 MB (9 files)320 KB (1 file)73%
Roboto Flex (full axes)2.8 MB (18 styles)480 KB (1 file)83%
Source Sans 3 (12 styles)1.6 MB (12 files)350 KB (1 file)78%
Open Sans (10 styles)900 KB (10 files)280 KB (1 file)69%

The variable file is always larger than a single static file (because it contains interpolation deltas), but it's dramatically smaller than the sum of all static files it replaces. The break-even point is typically 2-3 weights: if you use 3+ weights of the same font, the variable version is smaller.

HTTP Request Reduction

Beyond bytes, variable fonts reduce HTTP requests. Each static font file is a separate request with its own DNS/TCP/TLS overhead (for third-party hosts) or connection scheduling (for same-origin). With HTTP/2 multiplexing, the per-request overhead is small but not zero — each response still needs headers, and browsers limit concurrent requests per domain.

Going from 8 requests to 1 eliminates 7 round-trips of scheduling and header overhead. On mobile networks with high latency, this can save 200-500ms in total font loading time, independent of byte savings.

Design Possibilities

Variable fonts unlock typographic effects that were impractical with static fonts because they would have required downloading dozens of intermediate weight files.

Weight and Width Animation

CSS transitions and animations work with variable font properties:

h1 {
  font-weight: 400;
  transition: font-weight 0.3s ease;
}

h1:hover {
  font-weight: 700;
}

/* Smooth weight animation */
@keyframes breathe {
  from { font-weight: 300; }
  to { font-weight: 700; }
}
.animated { animation: breathe 2s ease-in-out infinite alternate; }

The browser interpolates between weights on every animation frame, producing smooth transitions that would be impossible with static fonts (which would snap between discrete weights). This is genuinely useful for hover effects, loading states, and interactive typography.

Responsive Typography

Variable fonts enable typography that adapts to viewport size without downloading extra files:

/* Thinner weight on mobile, heavier on desktop */
h1 {
  font-weight: clamp(400, 3vw + 350, 800);
  font-stretch: clamp(85%, 2vw + 80%, 110%);
}

/* Narrower text in tight containers */
.sidebar h2 {
  font-stretch: 80%;
  font-weight: 600;
}

This responsive capability has real UX value. Condensed widths can prevent awkward line breaks in narrow containers. Lighter weights work better at large display sizes. Heavier weights improve legibility at small sizes on low-contrast screens. All from one file.

Browser Support

Variable font support is at 96%+ globally as of 2026:

  • Chrome 66+ (March 2018)
  • Firefox 62+ (September 2018)
  • Safari 11+ (September 2017)
  • Edge 17+ (April 2018)

Safari was actually first, shipping support in macOS High Sierra and iOS 11. All major browsers have had support for 7+ years. The only environments that lack variable font support are IE11 (dead), very old Android WebView (rare), and some embedded browser engines in IoT devices.

Design tools also have excellent support: Figma, Sketch, Adobe CC (InDesign, Illustrator, Photoshop), and Affinity Designer all handle variable fonts natively. You can manipulate axes directly in the design tool's typography panel.

Fallback for Non-Supporting Browsers

If you must support browsers without variable font support (unlikely in 2026), provide a static fallback:

/* Static fallback */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Bold.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}

/* Variable override for supporting browsers */
@supports (font-variation-settings: normal) {
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/InterVariable.woff2') format('woff2');
    font-weight: 100 900;
    font-display: swap;
  }
}

The @supports (font-variation-settings: normal) test detects variable font support. Supporting browsers load the variable file; others load the static files. In practice, the static fallback is dead code for 96%+ of your users.

Google Fonts Variable Options

Google Fonts has aggressively shifted to variable fonts. As of 2026, over 1,400 families on Google Fonts include variable versions. When you request a variable font from Google Fonts, the API serves the variable file by default if your CSS URL uses the new axis syntax:

/* Variable (note the axis range syntax) */
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900" rel="stylesheet">

/* Static (specific weights listed) */
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700" rel="stylesheet">

The 100..900 range syntax triggers the variable font. Listing individual weights (400;700) triggers static files. For self-hosting (which is faster due to cache partitioning), download the variable WOFF2 from Google Fonts and serve it from your own domain.

Creating Variable Fonts with fonttools

If you have source files from a type design tool (Glyphs, FontForge, RoboFont), you can compile a variable font using fonttools:

# Install fonttools with variable font support
pip install fonttools brotli

# Compile from designspace (Glyphs/UFO sources)
fonttools varLib SourceSans3.designspace

# Convert to WOFF2
python -m fonttools ttLib -o SourceSans3-VF.woff2 SourceSans3-VF.ttf --flavor woff2

The designspace file defines the axes, masters (extreme designs), and instances (named stops like Regular, Bold). fonttools interpolates between masters to build the variation tables. This is the same pipeline that Google and Adobe use for their open-source variable fonts.

If you don't have the original source files and only have static TTF/OTF files, you cannot reliably merge them into a variable font. Variable fonts require compatible master designs with identical glyph point structures. Static fonts from different weight files may have incompatible outlines (different numbers of points, different contour structures), making automatic merging impossible without manual correction.

Variable fonts are the biggest improvement in web typography since WOFF2. The math is straightforward: if your design uses more than two weights of the same typeface, switching to the variable version saves bytes and HTTP requests. The creative upside — arbitrary weights, responsive widths, smooth animations — is a bonus on top of the performance win.

Start with your heaviest font family. Download or convert the variable WOFF2 (TTF to WOFF2, OTF to WOFF2), update your @font-face to declare weight ranges, and delete the individual weight files. Measure the before/after with WebPageTest or Lighthouse. The numbers will sell themselves.