Understanding Variable Fonts
A variable font is a single font file that behaves like multiple fonts. Instead of having separate files for each weight, width, or style variant, a variable font encapsulates an entire family of variations along one or more axes. Each axis represents a continuous range of a typographic property—such as weight, width, slant, or optical size—allowing you to dial in precisely the value you need.
Technologically, variable fonts are built on the OpenType Font Variations specification (part of OpenType 1.8). The font designer defines a set of master designs at the extremes of each axis, and the rendering engine interpolates smoothly between them. This means you can request, for example, a font weight of 347—something impossible with traditional discrete font files that only ship weights like 300, 400, 500, 600, and 700.
How Variable Fonts Differ from Traditional Fonts
With traditional web fonts, you typically load multiple files to cover different weights and styles:
Roboto-Regular.woff2Roboto-Bold.woff2Roboto-Italic.woff2Roboto-BoldItalic.woff2
Each file adds to the total download size. A variable font collapses all these variations into a single file. While the variable font file itself is typically larger than any one static file, it is almost always significantly smaller than the sum of all the static files it replaces. This is the core efficiency win.
Why Variable Fonts Matter
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →The advantages of variable fonts extend far beyond file-size savings. Here are the key benefits that make them a transformative tool for modern web typography:
1. Drastically Reduced Page Load Weight
A full family of static fonts might require 10–20 individual files totaling 1–2 MB or more. A variable font that covers the same range often clocks in at 300–600 KB. Fewer HTTP requests and less data transfer directly improve page performance, especially on slow connections.
2. Unlimited Design Flexibility
You are no longer constrained to the handful of weights the designer chose to ship. Need a weight of 437 to perfectly balance with a specific heading size? You can have it. Want to subtly adjust the width of a headline to fit a container without changing the font size? The wdth axis gives you that control. This granularity opens up responsive typography possibilities that were previously impossible on the web.
3. Improved Rendering and Consistency
Because the browser interpolates between masters, the transition between weights or styles is mathematically smooth. There are no awkward jumps where "Semi-Bold" looks noticeably different from "Bold" due to separate hinting or outlines. The visual rhythm remains consistent across the entire design system.
4. Dynamic Typography
Variable fonts can be animated and transitioned in real-time using CSS transitions or animations. Imagine a hover effect that smoothly increases the weight of a headline, or a width axis that responds to viewport changes—all without swapping font files. This interactivity brings a new dimension to web design.
Loading and Using Variable Fonts in CSS
To use a variable font, you first need to load it with an @font-face rule and then control its axes using either standard CSS properties or the low-level font-variation-settings property.
Step 1: The @font-face Declaration
When loading a variable font, you must explicitly tell the browser that the file contains variations. This is done using the format() hint with the variations suffix and the tech() function in newer syntax. You should also specify a range for the axes you intend to use via the font-weight, font-stretch, and font-style descriptors if the font supports those registered axes.
@font-face {
font-family: 'InterVariable';
src: url('InterVariable.woff2')
format('woff2')
tech('variations');
font-weight: 100 900;
font-stretch: 75% 125%;
font-style: oblique 0deg 10deg;
font-display: swap;
}
Let's break down what's happening here:
- font-family: A custom name you choose to reference the font.
- src: The URL to the variable font file. The
tech('variations')hint informs the browser that this file uses OpenType variations. The olderformat('woff2-variations')syntax still works for backwards compatibility buttech('variations')is the modern standard. - font-weight: 100 900: Declares that this font supports the weight axis from 100 to 900. This allows you to use any value in that range with the
font-weightproperty. - font-stretch: 75% 125%: Declares the supported width axis range, enabling the
font-stretchproperty. - font-style: oblique 0deg 10deg: For oblique/slant axes, this declares the supported slant angle range.
- font-display: swap: Recommended for performance; ensures text remains visible during font load.
Step 2: Controlling Axes with Standard CSS Properties
For the registered axes (weight, width, italic, slant, optical size), the best practice is to use the corresponding standard CSS properties. The browser maps these properties to the underlying font axes automatically.
/* Using standard CSS properties for registered axes */
.headline {
font-family: 'InterVariable', sans-serif;
font-weight: 785; /* Maps to the 'wght' axis */
font-stretch: 110%; /* Maps to the 'wdth' axis */
font-style: oblique 8deg; /* Maps to the 'slnt' axis */
font-size: 2.5rem;
}
.body-text {
font-family: 'InterVariable', sans-serif;
font-weight: 420;
font-stretch: 95%;
font-size: 1rem;
font-optical-sizing: auto; /* Maps to the 'opsz' axis automatically */
}
The beauty of using standard properties is readability, maintainability, and automatic fallback behavior. If the variable font fails to load, the browser can still apply these values to the fallback font stack (albeit with fewer granular options).
Step 3: Using font-variation-settings for Custom Axes
For axes that do not have a corresponding CSS property—typically custom axes defined by the font designer—you must use the font-variation-settings property. This property accepts a comma-separated list of axis tags and values.
/* Controlling custom axes with font-variation-settings */
.custom-display {
font-family: 'CustomSerifVariable', serif;
font-weight: 600;
font-variation-settings:
'GRAD' 150, /* Custom grade axis */
'CASL' 0.75, /* Custom casual axis (0-1 range) */
'CRSV' 1; /* Cursive axis: off (0) or on (1) */
}
/* Example: Recursive font with custom axes */
.recursive-mono {
font-family: 'RecursiveVariable', sans-serif;
font-weight: 500;
font-variation-settings:
'MONO' 1, /* Monospace: 0 (sans) to 1 (mono) */
'CASL' 0.5; /* Casualness: 0 (formal) to 1 (casual) */
}
Important: When you use font-variation-settings, always also set the corresponding standard property. For example, if you're setting the weight axis via font-variation-settings: 'wght' 700, also include font-weight: 700. This ensures fallback behavior works correctly. In fact, for registered axes, prefer the standard properties and only use font-variation-settings for custom axes.
Exploring Common Font Axes
Understanding the available axes is key to mastering variable fonts. Axes fall into two categories: registered (standardized across fonts) and custom (font-specific).
Registered Axes (Standardized)
These axes have four-character tags and corresponding CSS properties. When present, they should be controlled via CSS properties rather than raw font-variation-settings.
Weight Axis (wght) — Controlled via font-weight
The most common axis. Allows any weight value from 1 to 1000. The CSS property font-weight accepts numbers from 1 to 1000, giving you infinite precision between traditional keywords like normal (400) and bold (700).
.weight-demo {
font-family: 'InterVariable', sans-serif;
/* Any value from 1 to 1000 is valid */
font-weight: 347; /* Perfectly valid with a variable font */
}
Width Axis (wdth) — Controlled via font-stretch
Adjusts the horizontal width of glyphs as a percentage of the normal width. The range is typically from 50% (ultra-condensed) to 200% (ultra-expanded). The CSS property font-stretch accepts percentage values.
.width-demo {
font-family: 'InterVariable', sans-serif;
font-stretch: 85%; /* Slightly condensed */
}
.width-demo-wide {
font-family: 'InterVariable', sans-serif;
font-stretch: 118%; /* Moderately expanded */
}
Optical Size Axis (opsz) — Controlled via font-optical-sizing
Automatically adjusts glyph shapes for optimal rendering at different sizes. Small text benefits from thicker strokes and looser spacing; large display text can use finer details. The CSS property font-optical-sizing accepts auto or none. When set to auto, the browser automatically maps the font size to the optical size axis.
.optical-demo {
font-family: 'InterVariable', sans-serif;
font-optical-sizing: auto; /* Browser handles opsz automatically */
}
/* Manual control if needed (rarely recommended) */
.optical-manual {
font-family: 'InterVariable', sans-serif;
font-optical-sizing: auto;
/* For explicit control, use font-variation-settings sparingly */
font-variation-settings: 'opsz' 72;
}
Slant Axis (slnt) — Controlled via font-style: oblique
Provides a continuous slant angle for oblique text. Unlike the italic axis (which switches between separate glyph designs), slant is a mechanical tilt. Use font-style: oblique <angle> to specify the angle in degrees.
.slant-demo {
font-family: 'InterVariable', sans-serif;
font-style: oblique 6deg; /* Subtle slant */
}
.slant-heavy {
font-family: 'InterVariable', sans-serif;
font-style: oblique 12deg; /* More pronounced slant */
}
Italic Axis (ital) — Controlled via font-style: italic
A binary axis (0 or 1) that switches between roman and true italic glyphs. Unlike slant, italic uses purpose-drawn cursive glyphs. Use the standard font-style: italic property.
.italic-demo {
font-family: 'InterVariable', sans-serif;
font-style: italic; /* Maps to 'ital' 1 */
}
Custom Axes (Font-Specific)
Font designers can define any number of custom axes with four-character tags. These must be controlled via font-variation-settings. Here are examples from real-world variable fonts:
/* Recursive font — casual axis */
.recursive-casual {
font-family: 'RecursiveVariable', sans-serif;
font-variation-settings: 'CASL' 0.8; /* 0 = formal, 1 = casual */
}
/* Recursive font — monospace axis */
.recursive-mono {
font-family: 'RecursiveVariable', sans-serif;
font-variation-settings: 'MONO' 1; /* 0 = proportional, 1 = monospace */
}
/* Fraunces font — wonky axis (playful character) */
.fraunces-playful {
font-family: 'FrauncesVariable', serif;
font-variation-settings: 'WONK' 1; /* 0 = normal, 1 = wonky */
}
/* Comfortaa font — fun axis */
.comfortaa-fun {
font-family: 'ComfortaaVariable', sans-serif;
font-variation-settings: 'FUNN' 1;
}
Always consult the font's documentation or specimen page to discover available custom axes and their valid ranges. Each font is unique in what it offers.
Best Practices for Variable Fonts in Production
1. Always Provide a Fallback Font Stack
Variable fonts are widely supported in modern browsers, but you must always include a sensible fallback. The fallback should approximate the design intent using standard fonts.
/* Robust fallback stack */
body {
font-family: 'InterVariable', 'Inter', 'Helvetica Neue',
Helvetica, Arial, sans-serif;
font-weight: 400;
font-stretch: 100%;
}
/* For headings with higher weight */
h1 {
font-family: 'InterVariable', 'Inter', 'Helvetica Neue',
Helvetica, Arial, sans-serif;
font-weight: 750; /* Falls back to 700 (bold) in static fonts */
}
2. Use Standard CSS Properties Whenever Possible
For registered axes, always prefer the standard CSS properties (font-weight, font-stretch, font-style) over font-variation-settings. This ensures:
- Fallback fonts can still apply approximate styling
- Your CSS is more readable and maintainable
- Browser DevTools can inspect and modify values easily
/* GOOD: Use standard properties */
.good-example {
font-weight: 650;
font-stretch: 110%;
font-style: oblique 5deg;
}
/* AVOID: Overusing font-variation-settings for registered axes */
.bad-example {
font-variation-settings: 'wght' 650, 'wdth' 110, 'slnt' 5;
/* Fallback fonts will completely ignore this */
}
3. Set Explicit Ranges in @font-face
Always declare the supported ranges for weight, stretch, and style in your @font-face rule. This enables the browser to correctly map CSS property values to axis values and helps with font selection when multiple fonts are available.
@font-face {
font-family: 'MyVariableFont';
src: url('MyVariableFont.woff2')
format('woff2')
tech('variations');
font-weight: 100 900; /* Min and max weight */
font-stretch: 60% 140%; /* Min and max width */
font-style: oblique 0deg 15deg; /* Min and max slant */
font-display: swap;
}
4. Optimize Font Loading with font-display
Variable font files are larger than individual static files. Use font-display: swap to prevent invisible text during font loading (FOIT). For critical above-the-fold text, consider font-display: fallback or preloading the font.
/* Preloading a variable font for critical rendering */
/* Place this in your HTML
*/
@font-face {
font-family: 'InterVariable';
src: url('InterVariable.woff2')
format('woff2')
tech('variations');
font-weight: 100 900;
font-display: swap; /* Text renders immediately with fallback */
}
5. Use WOFF2 with Unicode Range Subsetting
For large variable fonts, consider using unicode-range to create subsets for different character sets (Latin, Cyrillic, Greek, etc.). This is especially valuable for fonts with extensive language support.
/* Subsetting a variable font by unicode range */
@font-face {
font-family: 'InterVariable';
src: url('InterVariable-Latin.woff2')
format('woff2')
tech('variations');
font-weight: 100 900;
unicode-range: U+0000-00FF, U+0131, U+0152-0153,
U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+2000-206F, U+2074, U+20AC, U+2122,
U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
font-display: swap;
}
@font-face {
font-family: 'InterVariable';
src: url('InterVariable-Cyrillic.woff2')
format('woff2')
tech('variations');
font-weight: 100 900;
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1,
U+2116;
font-display: swap;
}
6. Avoid Excessive Axis Changes in Animations
While variable fonts can be animated, each axis change triggers font recalc and repaint. Limit animations to one or two axes at a time, and prefer will-change sparingly to hint the browser about upcoming changes.
/* Subtle hover animation on weight only */
.animated-headline {
font-family: 'InterVariable', sans-serif;
font-weight: 400;
transition: font-weight 0.3s ease;
}
.animated-headline:hover {
font-weight: 700;
}
/* Complex multi-axis animation — use cautiously */
.complex-animation {
font-family: 'InterVariable', sans-serif;
font-weight: 400;
font-stretch: 100%;
transition: font-weight 0.4s ease,
font-stretch 0.4s ease;
}
.complex-animation:hover {
font-weight: 800;
font-stretch: 120%;
}
Advanced Techniques and Creative Applications
Responsive Typography with Variable Fonts
Combine variable font axes with CSS custom properties and viewport units to create typography that responds fluidly to screen size. This goes far beyond simple font-size scaling.
/* Fluid weight based on viewport width */
:root {
--fluid-weight: 400;
--fluid-width: 100;
}
@media (min-width: 320px) and (max-width: 1200px) {
:root {
/* Weight scales from 400 to 700 across the viewport range */
--fluid-weight: calc(400 + (700 - 400) *
((100vw - 320px) / (1200 - 320)));
/* Width scales from 90% to 110% */
--fluid-width: calc(90 + (110 - 90) *
((100vw - 320px) / (1200 - 320)));
}
}
.fluid-headline {
font-family: 'InterVariable', sans-serif;
font-weight: var(--fluid-weight);
font-stretch: calc(var(--fluid-width) * 1%);
font-size: clamp(1.5rem, 4vw, 4rem);
}
Integrating with CSS Custom Properties for Design Systems
Define your typographic scale using custom properties that map to variable font axes. This centralizes control and makes global adjustments trivial.
/* Design system typography tokens */
:root {
--font-family-base: 'InterVariable', sans-serif;
--font-weight-body: 420;
--font-weight-heading: 750;
--font-stretch-body: 100%;
--font-stretch-heading: 105%;
--font-optical-sizing: auto;
}
/* Apply tokens globally */
body {
font-family: var(--font-family-base);
font-weight: var(--font-weight-body);
font-stretch: var(--font-stretch-body);
font-optical-sizing: var(--font-optical-sizing);
line-height: 1.6;
}
h1, h2, h3 {
font-family: var(--font-family-base);
font-weight: var(--font-weight-heading);
font-stretch: var(--font-stretch-heading);
font-optical-sizing: var(--font-optical-sizing);
}
/* Override for specific contexts */
.card-title {
font-weight: 650; /* Slightly lighter than main headings */
font-stretch: 102%;
}
Dark Mode Adjustments Using the Grade Axis
Some variable fonts include a GRAD (grade) axis that adjusts glyph weight without changing the overall width or affecting kerning. This is perfect for compensating for the optical illusion where light text on dark backgrounds appears thinner.
/* Grade adjustment for dark mode */
body {
font-family: 'InterVariable', sans-serif;
font-weight: 400;
font-variation-settings: 'GRAD' 0;
}
@media (prefers-color-scheme: dark) {
body {
/* Increase grade to maintain perceived weight on dark backgrounds */
font-variation-settings: 'GRAD' 50;
/* font-weight remains 400 — no layout shift */
}
}
Subtle Interactive Micro-Animations
Use CSS transitions on variable font axes to create delightful micro-interactions that enhance user experience without being distracting.
/* Interactive link styling with weight animation */
.interactive-link {
font-family: 'InterVariable', sans-serif;
font-weight: 450;
text-decoration: none;
color: #0066cc;
transition: font-weight 0.2s ease-out;
}
.interactive-link:hover {
font-weight: 650;
text-decoration: underline;
}
/* Button with combined weight and width transition */
.animated-button {
font-family: 'InterVariable', sans-serif;
font-weight: 500;
font-stretch: 100%;
padding: 0.75rem 1.5rem;
border: 2px solid currentColor;
border-radius: 0.5rem;
cursor: pointer;
transition: font-weight 0.25s ease,
font-stretch 0.25s ease;
}
.animated-button:hover {
font-weight: 700;
font-stretch: 110%;
}
Browser Support and Progressive Enhancement
Variable fonts are supported in all modern browsers, including Chrome, Firefox, Safari, and Edge. As of 2024, global support exceeds 95% of web traffic. However, implementing progressive enhancement ensures a graceful experience for all users.
/* Progressive enhancement approach */
@font-face {
font-family: 'Inter';
src: url('Inter-Regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('Inter-Bold.woff2') format('woff2');
font-weight: 700;
font-display: swap;
}
/* Override with variable font if supported */
@supports (font-variation-settings: normal) {
@font-face {
font-family: 'Inter';
src: url('InterVariable.woff2')
format('woff2')
tech('variations');
font-weight: 100 900;
font-stretch: 75% 125%;
font-display: swap;
}
/* Now you can use arbitrary weights */
body {
font-weight: 420;
}
}
/* Fallback for browsers without variable font support */
body {
font-family: 'Inter', sans-serif;
font-weight: 400; /* Safe fallback */
}
This approach loads static fonts for all browsers but overrides with the variable font when supported. The @supports rule ensures that only browsers capable of interpreting variable fonts download the variable file.
Performance Considerations and Testing
While variable fonts reduce total file count, the single file can be substantial. Here are key performance strategies:
- Measure before optimizing: Use browser DevTools to measure font download times and rendering impact.
- Subset aggressively: If your site only uses Latin characters, strip out Cyrillic, Greek, and other scripts using a font subsetting tool like glyphhanger or fonttools.
- Prefer WOFF2: WOFF2 compression is significantly more efficient than WOFF or TrueType formats, especially for variable fonts.
- Use
font-display: swap: Ensures text is visible immediately while the font loads in the background. - Preload critical fonts: For fonts used above the fold, use a
<link rel="preload">tag to prioritize loading. - Monitor CLS (Cumulative Layout Shift): Font swapping can cause layout shifts. Choose fallback fonts with similar metrics, and use
size-adjustin@font-faceif needed.
/* Using size-adjust to minimize layout shift with fallback */
@font-face {
font-family: 'InterVariable';
src: url('InterVariable.woff2')
format('woff2')
tech('variations');
font-weight: 100 900;
font-display: swap;
/* Adjust fallback font metrics to match */
size-adjust: 105%;
ascent-override: 95%;
descent-override: 25%;
}
Debugging Variable Fonts in DevTools
Modern browser DevTools provide excellent support for inspecting and debugging variable fonts. In Chrome DevTools:
- Select an element using a variable font in the Elements panel.
- In the Styles pane, look for the font-related properties.
- Click the small wrench icon next to
font-variation-settingsto open an interactive slider interface for all available axes. - You can drag sliders to visually explore axis ranges in real-time.
This interactive exploration is invaluable for finding the perfect axis values before committing them to your CSS.
Conclusion
CSS variable fonts represent one of the most significant advances in web typography since @font-face itself. They collapse entire font families into single, efficient files while unlocking infinite design granularity across weight, width, slant, optical size, and countless custom axes. By using standard CSS properties for registered axes, providing robust fallbacks, optimizing loading strategies, and thoughtfully integrating variable fonts into your design system, you can deliver typography that is both beautiful and performant. The techniques covered in this tutorial—from fluid responsive scaling to dark-mode grade adjustments and subtle interactive animations—demonstrate that variable fonts are not merely a file-size optimization but a creative tool that expands what's possible with type on the web. As browser support has become ubiquitous and the ecosystem of high-quality variable fonts continues to grow, there has never been a better time to adopt variable fonts as a core part of your front-end toolkit.