Skip to content
Chapter 17Systems7 min read

Variable Fonts Deep Dive

How variable fonts work under the hood — axes, performance trade-offs, CSS implementation, and production strategies for modern type systems.

Variable fonts compress an entire type family — weights, widths, optical sizes, sometimes slant and custom axes — into a single font file. Instead of loading Inter Regular, Medium, SemiBold, and Bold as four files, one Inter variable font spans weight 100–900 continuously. For designers, that means infinite intermediate weights. For developers, it means fewer HTTP requests and finer responsive control. For everyone, it means typography that was impossible with static files is now a CSS property away.

How variable fonts work

Traditional fonts are static instances — discrete masters at specific coordinates (e.g., Regular at weight 400, Bold at 700). Variable fonts store outline deltas from default positions along axes defined at design time. The rendering engine interpolates between extremes at runtime.

Registered axes

The OpenType spec defines four standard axes:

| Axis | CSS property | Tag | Typical range | |------|-------------|-----|---------------| | Weight | font-weight | wght | 100–900 | | Width | font-stretch | wdth | 75%–125% | | Slant | font-style (oblique) | slnt | −12°–0° | | Italic | font-style | ital | 0–1 |

Weight (wght) is the most commonly used axis. Setting font-weight: 573 is valid with a variable font — the engine renders an interpolated stroke weight between masters.

Width (wdth) compresses or expands letterforms — useful for narrow sidebars or responsive headlines without changing point size.

Slant (slnt) and Italic (ital) differ: true italic uses alternate glyphs; slant mechanically skews. Variable fonts may offer both.

Custom axes

Type designers can define proprietary axes — grade (GRAD) adjusts weight without changing spacing (useful for dark mode), optical size (opsz) shifts proportions for text vs display sizes, serif (SRFF) morphs between sans and serif in experimental faces.

Explore axes interactively:

Variable Font Playground
Variable typography in motion
font-variation-settings: "wght" 400, "wdth" 100, "slnt" 0;

Performance: fewer files, new trade-offs

Benefits

  • One file replaces many — Inter VF replaces 18 static files in a full family cut.
  • Fewer HTTP requests — critical on HTTP/1.1; still helpful on HTTP/2 where request count affects prioritization.
  • Smaller total payload when using 3+ weights — the delta compression wins over duplicate outlines.
  • Animation — smooth weight transitions for hover states and loading indicators.

Costs

  • Single file can be larger than one static weight — if you only need Regular, a variable file may download more data than Inter-Regular.woff2.
  • Parsing complexity — low-end devices spend slightly more CPU interpolating glyphs.
  • Subset limitations — subsetting variable fonts is improving but still less mature than static subsetting.

Rule of thumb: Use variable fonts when you need 3+ weights or widths, or when animating axes. Ship static files when you need one weight on bandwidth-constrained surfaces (email, embedded widgets).

Variable for marketing siteGood

Hero animates weight 300→600 on scroll. Body at 400, emphasis at 550 — a weight impossible in static files. One 120KB WOFF2 file replaces four 45KB files.

Variable for single-weight appPoor

Dashboard uses only 400 weight everywhere but loads a 180KB variable font because "it's modern." Slower LCP, no visual benefit.

CSS implementation

Loading variable fonts

css
{`@font-face {
  font-family: "Inter Variable";
  src: url("/fonts/InterVariable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-stretch: 75% 125%;
  font-style: normal;
  font-display: swap;
}`}

The font-weight range tells the browser this file covers the full span. Omitting the range causes synthetic bolding or fallback.

Using axes in CSS

css
{`.headline {
  font-family: "Inter Variable", sans-serif;
  font-weight: 650;
  font-stretch: 95%;
}

.experimental {
  font-variation-settings: "wght" 450, "wdth" 110, "slnt" -5;
}

/* Prefer high-level properties when available */
.caption {
  font-weight: 500;
  font-stretch: 105%;
}`}

Use font-weight and font-stretch when they map to registered axes. Fall back to font-variation-settings for custom axes — but reset other axes explicitly when mixing, because font-variation-settings overrides implicit defaults.

Optical sizing

Some serif variable fonts expose opsz. At small sizes, contrast decreases and x-height increases — mimicking centuries of optical size masters:

css
.body-text {
  font-variation-settings: "opsz" 14;
}

.display-title {
  font-variation-settings: "opsz" 72;
}

Google Fonts' font-optical-sizing: auto enables automatic opsz when the face supports it.

Responsive design with variable fonts

Variable fonts pair naturally with fluid typography from the previous chapter:

css
{`.hero {
  font-weight: clamp(400, 300 + 2vw, 700);
  font-stretch: clamp(95%, 90% + 0.5vw, 100%);
}`}

On narrow viewports, slightly heavier weight and expanded width maintain legibility without shrinking font size below accessible minimums.

Animation and interaction

Transition weight on hover for tactile buttons:

css
.button {
  font-weight: 500;
  transition: font-weight 0.2s ease;
}

.button:hover {
  font-weight: 600;
}

Keep animations subtle — weight shifts above 100 units feel like layout shift. Combine with font-variation-settings for custom axis demos, always respecting prefers-reduced-motion.

Fallback strategy

Not every environment supports variable fonts. Build a robust stack:

css
{`@font-face {
  font-family: "Inter Fallback";
  src: url("/fonts/Inter-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

@supports (font-variation-settings: normal) {
  @font-face {
    font-family: "Inter";
    src: url("/fonts/InterVariable.woff2") format("woff2-variations");
    font-weight: 100 900;
  }
}

body {
  font-family: "Inter", "Inter Fallback", system-ui, sans-serif;
}`}

For @supports failures, static Regular and Bold files cover 95% of cases. Test with variable fonts disabled in Firefox DevTools.

Design system integration

Document variable font axes in your token file:

css
:root {
  --font-weight-body: 400;
  --font-weight-emphasis: 550;
  --font-weight-heading: 700;
  --font-width-compressed: 90%;
  --font-width-normal: 100%;
}

Figma's variable font support mirrors CSS axes — keep token names aligned between design and code. When handing off, specify which weights are brand-approved even though infinite values exist — otherwise engineers pick 637 and designers pick 612.

Notable variable fonts to study

  • Inter Variable — workhorse sans, excellent web hinting, full weight range.
  • Roboto Flex — parametric sans with weight, width, slant, and optical size.
  • Source Serif 4 — editorial serif with opsz for text/display optimization.
  • Recursive — mono/sans hybrid with CASL, CRSV, and MONO axes for code and UI.
  • Helvetica Now Variable — commercial benchmark for neo-grotesque interpolation.

Each demonstrates different axis strategies — study the fonts that match your category before commissioning custom variable work.

Production checklist

  • [ ] @font-face declares full weight/stretch ranges
  • [ ] font-display: swap set; preload only critical weights
  • [ ] Static fallback for unsupported browsers
  • [ ] Token limits on approved axis values
  • [ ] Dark mode tested; GRAD or weight bump if needed
  • [ ] Animations respect prefers-reduced-motion
  • [ ] Payload compared against equivalent static files
  • [ ] Licensing covers variable font web embedding

Variable fonts are not a novelty — they are the default delivery format from major foundries. Understanding axes, performance, and CSS wiring lets you use the full expressive range without sacrificing speed or accessibility. Start with one variable workhorse sans, define your tokens, and expand into width and optical axes as your system matures.