Skip to main content
  • Created:
  • Updated:
  • Author:
    Takeshi Takatsudo

Variable Fonts

The Problem

Traditional web typography requires loading separate font files for each weight, width, and style combination. A typical project might load regular, bold, italic, and bold-italic variants — four files — just for body text. AI agents commonly generate CSS that references multiple static font weights (300, 400, 500, 600, 700) with separate @font-face declarations, resulting in five or more HTTP requests and significantly larger total download sizes. Variable fonts solve this by packing an entire range of variations into a single file.

The Solution

Variable fonts contain one or more axes of variation — continuous ranges for properties like weight, width, and slant. A single variable font file replaces multiple static files, reducing network requests and enabling smooth transitions between any values along those axes. The CSS font-variation-settings property provides low-level control, while standard CSS properties (font-weight, font-stretch, font-style) now accept ranges and map directly to registered axes.

Registered Axes

Axis tagCSS propertyDescriptionExample range
wghtfont-weightWeight (thin to black)100–900
wdthfont-stretchWidth (condensed to expanded)75%–125%
slntfont-styleSlant angle-12deg–0deg
italfont-styleItalic (binary toggle)0 or 1
opszfont-optical-sizingOptical size adjustments8–144

Code Examples

Basic Variable Font Setup

@font-face {
font-family: "Inter";
src: url("/fonts/Inter-Variable.woff2") format("woff2-variations");
font-weight: 100 900; /* Declare the full weight range */
font-display: swap;
}

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

h1 {
font-weight: 750; /* Any value in the range — not limited to 100-step increments */
}

.light-text {
font-weight: 350;
}

.bold-text {
font-weight: 680;
}
Variable Font Weight Range — Fine-grained Weight Control

Using Standard CSS Properties (Preferred)

/* CORRECT: Use standard CSS properties for registered axes */
h1 {
font-weight: 800;
font-stretch: 110%;
font-style: oblique 8deg;
}

/* AVOID: Low-level font-variation-settings for registered axes */
h1 {
font-variation-settings: "wght" 800, "wdth" 110, "slnt" -8;
}

Standard properties are preferred because they cascade properly, work with inherit and initial, and don't override each other. With font-variation-settings, setting one axis resets all others to their defaults.

Custom Axes

Custom axes (identified by uppercase tags) require font-variation-settings:

/* GRAD = Grade axis (custom), adjusts stroke weight without changing width */
.dark-bg-text {
font-variation-settings: "GRAD" 150;
}

/* CASL = Casual axis in Recursive font */
.casual-text {
font-variation-settings: "CASL" 1;
}

/* Combining custom axes with standard properties */
.display-text {
font-weight: 700;
font-variation-settings: "GRAD" 100, "CASL" 0.5;
}

Responsive Weight with Custom Properties

:root {
--heading-weight: 700;
--body-weight: 400;
}

@media (max-width: 768px) {
:root {
--heading-weight: 600; /* Slightly lighter on small screens for readability */
--body-weight: 420; /* Slightly heavier for small screen legibility */
}
}

h1,
h2,
h3 {
font-weight: var(--heading-weight);
}

body {
font-weight: var(--body-weight);
}

Animated Font Variations

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

.hover-weight:hover {
font-weight: 700;
}

/* Smooth weight animation — impossible with static fonts */
@keyframes breathe {
0%,
100% {
font-weight: 300;
}
50% {
font-weight: 700;
}
}

.animated-text {
animation: breathe 3s ease-in-out infinite;
}

Optical Sizing

/* Automatic optical sizing (on by default when the font supports it) */
body {
font-optical-sizing: auto;
}

/* Manual control for specific cases */
.small-caption {
font-size: 0.75rem;
font-optical-sizing: auto; /* Font adjusts stroke contrast for small size */
}

.display-hero {
font-size: 4rem;
font-optical-sizing: auto; /* Font adjusts for large display size */
}

Progressive Enhancement with @supports

/* Fallback: static font files */
@font-face {
font-family: "MyFont";
src: url("/fonts/myfont-regular.woff2") format("woff2");
font-weight: 400;
}

@font-face {
font-family: "MyFont";
src: url("/fonts/myfont-bold.woff2") format("woff2");
font-weight: 700;
}

/* Variable font override for supporting browsers */
@supports (font-variation-settings: normal) {
@font-face {
font-family: "MyFont";
src: url("/fonts/myfont-variable.woff2") format("woff2-variations");
font-weight: 100 900;
}
}

Dark Mode Weight Compensation

/* Text on dark backgrounds appears heavier — reduce weight to compensate */
@media (prefers-color-scheme: dark) {
body {
font-weight: 350; /* Lighter than the 400 used in light mode */
}

h1 {
font-weight: 650; /* Lighter than the 700 used in light mode */
}
}

Common AI Mistakes

  • Loading multiple static font files (regular, medium, semibold, bold) instead of a single variable font file, multiplying HTTP requests unnecessarily
  • Using font-variation-settings for registered axes (weight, width, slant) instead of standard CSS properties — this breaks cascading and resets unspecified axes
  • Not declaring the weight range in @font-face (e.g., font-weight: 100 900), causing browsers to only use the default weight
  • Treating variable font weights like static fonts — only using values at 100-step increments (400, 500, 600) when any value in the range is valid
  • Not compensating for text appearing heavier on dark backgrounds — variable fonts make it easy to subtract 30–50 weight units for dark mode
  • Forgetting that font-variation-settings values all reset when you set any one axis — each declaration must include every axis you want to control
  • Using format("woff2") instead of format("woff2-variations") in the @font-face src descriptor, though most modern browsers accept either

When to Use

Variable fonts are ideal for

  • Projects using 3+ weights of the same font family — the single file is typically smaller than multiple static files
  • Designs that need fine-grained weight control (e.g., 350, 450, 550)
  • Animations or transitions involving weight, width, or slant changes
  • Dark mode designs where weight compensation improves readability
  • Responsive designs that adjust weight based on viewport size or context

Stick with static fonts when

  • Only 1-2 weights are needed — a single static file may be smaller than the variable version
  • The chosen typeface is not available as a variable font
  • Legacy browser support is a hard requirement (IE11)

References