A variable font is a single font file that contains a continuous range of designs along one or more axes, such as weight (wght) or width (wdth). Instead of loading separate files for regular, medium and bold, you declare a range in @font-face and then pick any value with ordinary CSS such as font-weight: 560.
A range instead of separate files
A traditional font family is a folder of files: regular, italic, medium, bold, condensed bold and so on. Each one is a fixed design, and each one is a separate download. You can only use the weights somebody chose to export.
A variable font stores the extremes of a design and the rules for moving between them. The type designer draws, for example, the thinnest and the heaviest version, and the font describes how every point travels from one to the other. Your browser can then render any position along that line: 400, 700, or 537 if that is what the design needs.
Each of those lines is called an axis. The available axes and their ranges depend on the font. Not every variable family supports every axis, and many have only one: weight.
The five registered axes
Five axes are standardized. Each has a four-letter lowercase tag and a matching high-level CSS property, so most of the time you never write the tag at all.
| Axis | Tag | CSS property | Example |
|---|---|---|---|
| Weight | wght | font-weight | font-weight: 560. Any number from 1 to 1000 that the font supports |
| Width | wdth | font-stretch, being renamed to font-width | font-stretch: 85%. Percentages, where 100% is normal width |
| Optical size | opsz | font-optical-sizing | font-optical-sizing: auto, which is the default. The browser matches the axis to the font size |
| Slant | slnt | font-style: oblique with an angle | font-style: oblique 10deg |
| Italic | ital | font-style: italic | font-style: italic. The axis is a switch: 0 is off, 1 is on |
- Optical size is more than scaling. At small sizes the font can open up its spacing and thicken its thin strokes. At large sizes it can sharpen the details. Because the default is
auto, you get this without writing any CSS when the font has the axis. - Slant and italic are different. Slant leans the upright letters by an angle. Italic swaps in true italic letterforms, such as a single-story a. Many families ship upright and italic as two separate variable files instead of an
italaxis. - The slant sign is reversed.
font-style: oblique 10degcorresponds to aslntvalue of -10, because the axis counts counter-clockwise angles as positive. It is one more reason to use the high-level property. - `font-width` is the new name.
font-stretchremains as a legacy alias with broader browser support today. If you use the new name, writefont-stretchfirst as a fallback.
Custom axes
Type designers can add any axis they like. Custom axes use uppercase four-letter tags, which is how you tell them apart from registered ones. Tags are case-sensitive. Fraunces has SOFT and WONK. Recursive has CASL for casual, MONO and CRSV for cursive. Roboto Flex has GRAD for grade, among many others. Custom axes have no CSS property of their own, so they are set with font-variation-settings. Check the font’s own documentation for tags and limits. Do not guess them.
Setting up a variable font with @font-face
The one thing that differs from a static font: the descriptors take a range. Two numbers in font-weight tell the browser that this one file covers everything between them.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-variable.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter-variable-italic.woff2") format("woff2");
font-weight: 100 900;
font-style: italic;
font-display: swap;
}
body {
font-family: "Inter", system-ui, sans-serif;
font-weight: 400;
}
h1 {
font-weight: 650;
}
strong {
font-weight: 560;
}If you leave the range out, the browser assumes the file is a single regular weight. Ask for bold and it may smear a fake bold over the regular outlines instead of using the axis. The same goes for width: declare font-stretch: 75% 125% in @font-face if the font has a wdth axis with that range. Faux styles and other loading problems are covered in CSS font stacks and fallbacks.
format("woff2-variations"). That hint was needed while browsers were adding support. A plain format("woff2") is understood by every current browser.Google Fonts: the axis range syntax
With the Google Fonts css2 API you request a range by joining two values with two dots. If you list single weights instead, you get only those weights.
<!-- The full weight axis -->
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"
rel="stylesheet">
<!-- Two axes: upright and italic, each with a weight range -->
<link
href="https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet">font-variation-settings and its pitfalls
font-variation-settings is the low-level way to set any axis by tag. Prefer the usual CSS properties where they exist, and keep this one for custom axes. It has three traps.
- It is all or nothing. The property is one value, not one value per axis. A new declaration replaces the whole list, and every axis you do not mention returns to its default.
- It wins over the friendly properties. A
wghtset here overridesfont-weight, no matter where each appears in the cascade. A stray"wght" 400can silently cancel every bold on the page. - It knows nothing about fallbacks.
font-weight: 700still produces bold text when the variable font fails to load. Awghtsetting does nothing for the fallback font.
/* The problem */
.soft { font-variation-settings: "SOFT" 100; }
.wonky { font-variation-settings: "WONK" 1; }
/* <h1 class="soft wonky"> is wonky but not soft:
the second declaration replaced the first. */The fix is to write the full list once and feed it from CSS custom properties. Custom properties do cascade one by one, so each class only changes its own axis.
h1,
h2,
h3 {
font-variation-settings:
"SOFT" var(--soft, 0),
"WONK" var(--wonk, 0);
}
.soft {
--soft: 100;
}
.wonky {
--wonk: 1;
}Performance and animation
Is a variable font faster?
It depends on how many styles you use. A variable file holds many designs, so it is typically larger than any single static file. It pays off when it replaces several of them.
| Your page uses | Better choice |
|---|---|
| One or two weights of a family | Static files are usually smaller |
| Three or more weights, or weights plus widths | One variable file is usually smaller, and it is a single request |
| Fine steps such as 450 or 560, or responsive weight | Variable. Static fonts cannot do this at all |
Compare the actual transfer size in the Network panel for your own font, since the numbers vary a lot between families. Subsetting to the scripts you need matters as much as the choice between variable and static. And a continuous range does not mean you should use all of it. A design system can expose a few considered settings, which keeps the page coherent. Map them to the steps of your typography scale.
Animating an axis
.nav-link {
font-weight: 400;
transition: font-weight 200ms ease;
}
.nav-link:hover {
font-weight: 650;
}
@media (prefers-reduced-motion: reduce) {
.nav-link {
transition: none;
}
}Axes interpolate smoothly, so transitions and keyframe animations work on font-weight, font-stretch and font-variation-settings. One caution: heavier letters are wider, so animating weight makes the text reflow and nudges its neighbors. A grade axis, where the font has one, changes the apparent weight without changing the width, which makes it the better choice for hover effects.
Browser support
Variable fonts work in all current versions of Chrome, Edge, Firefox and Safari, on desktop and mobile, and have for years. A fallback is only a concern for very old browsers. Test the lightest and heaviest settings, small text, and every language your project supports.
How to tell whether a site uses a variable font
A weight such as 450 or 620 in the computed styles is a strong hint, because static families almost never ship those. To be sure, check one of these.
- Firefox. Right-click the text, choose Inspect, and open the Fonts tab of the Inspector. For a variable font, Firefox shows a slider for each axis. You can drag the sliders to try other values live on the page.
- Chrome and Edge. Open DevTools, go to the Network panel and filter by Font. One file serving several weights suggests a variable font. Then search the CSS for the
@font-facerule. A weight range such asfont-weight: 100 900confirms it. - Any browser. Select the text in the inspector and look for
font-variation-settingsin the computed styles. Remember that a site can use a variable font throughfont-weightalone, so an empty value proves nothing. - Safari. The Font panel in the details sidebar of the Elements tab lists the variation axes of the primary font.
Browser-specific steps are in finding fonts in Firefox, Safari and Edge.
See the axes a page is actually using.
Click any text and Font Inspector shows its variable font axes next to the family, weight and size, whenever the page uses them. No digging through stylesheets.
Font Inspector lists the variable axes in its panel when the page uses them, alongside the rendered font, the weight as a number and a name, and the source of the family. That makes it easy to see not only that a font is variable, but which settings the designers chose. If the family comes from Google Fonts, Get this font opens it there, where you can see every axis and its range. Start with how to identify fonts on a website if you are new to inspecting type.
Questions & answers
What is a variable font?+
It is a single font file that contains a continuous range of styles along one or more axes, such as weight, width or slant. One file can replace many static files, and you can pick any value in the range with CSS.
How do I use a variable font in CSS?+
Declare it with @font-face and give font-weight a range, such as font-weight: 100 900. Then use normal properties such as font-weight: 560. Use font-variation-settings only for custom axes.
What is the wght axis?+
wght is the registered tag for the weight axis. It is controlled by the font-weight property and accepts any number from 1 to 1000 within the range the font supports.
Why is my font-variation-settings being ignored or reset?+
The property is not additive. Each declaration replaces the whole list, so axes you leave out return to their defaults. Put every axis in one declaration and drive the values with CSS custom properties.
Are variable fonts supported in all browsers?+
Yes, in all current versions of Chrome, Edge, Firefox and Safari, including mobile. Only very old browsers need a static fallback.
Related free tools: Type scale calculator
Sources and further reading: MDN: Variable fonts guide ↗ · MDN: font-variation-settings ↗ · MDN: font-width ↗ · web.dev: Introduction to variable fonts on the web ↗ · Google Fonts: CSS API update (css2) ↗ · Firefox DevTools: fonts ↗