Using Variable Fonts: Understanding Axes, File Size Trade-offs, and Where Browser Support Is Limited
Variable fonts offer axis control and potential file-size savings, but the savings only materialise when multiple weights are used. Learn the five registered axes, the dead-weight problem, and where browser support fails.
Using Variable Fonts: Understanding Axes, File Size Trade-offs, and Where Support Is Limited
You have loaded a variable font, written the CSS, and every headline still sits at the regular weight. No bold, no light, no in-between. That is the moment variable fonts stop being a promise and start being a debugging exercise. The cause is almost never the font file itself. It is the axis declaration the rendering engine either ignored or never received. You fix it by learning exactly what the five registered OpenType axis tags control, what the font-variation-settings CSS property expects, and where the operating system simply will not cooperate.
The five registered axes are the whole game. Weight (wght) maps to the CSS font-weight scale of 100 to 900. Width (wdth) maps to font-stretch percentages. Optical size (opsz) adjusts letterforms for point sizes. Slant (slnt) produces oblique forms. Italic (ital) is a binary switch between upright and italic. Nothing else is guaranteed across fonts. A foundry can add custom axes, grade, x-height, serif length, even wonkiness, but those are not registered, not portable, and not animatable in any standardised way. If you use a custom axis, you are on your own.
Support is better than it was but still has holes. All major desktop clients, Chrome, Firefox, Safari, Edge, have handled variable fonts since 2018. The failures now live at the edges. Android system fonts do not support variable axes; a font like Roboto Flex will render as a static instance. Safari's font-variation-settings implementation has known parsing differences, particularly around the order of declarations and how it handles axis tags it does not recognise. Omit font-variation-settings entirely, or use syntax the client rejects, and every character falls back to the default axis position. That is why your 900-weight headline looks like a 400. The fix is always the same: declare the axis explicitly, test in the client that matters, and check caniuse.com before you commit.
OpenType Variable Font Axes: The Five Registered Tags and What Each Controls
Weight, Width, Optical Size, Slant, and Italic
The OpenType 1.8 specification introduced variable fonts, and with it five registered axis tags that every variable font must respect. These are the vocabulary the rendering engine understands. The weight axis (wght) ranges from 1 to 1000, mapping cleanly onto the CSS font-weight scale where 100 is Thin and 900 is Black. The width axis (wdth) runs from 50 to 200 percent of normal width, so 100 is regular, 75 is condensed, and 200 is expanded. The optical size axis (opsz) spans 6 to 72 points, and it changes the letterforms themselves: thinner strokes and tighter spacing for small text, more contrast and open counters for display sizes. The slant axis (slnt) goes from 0 to 20 degrees, producing oblique forms without a separate file. The italic axis (ital) is the odd one out: it is binary, 0 for upright and 1 for italic, because italic is not a smooth continuum but a different set of letterforms.
What the Registered Axes Do Not Cover
What the registered axes do not cover is where designers get confused. There is no registered axis for x-height, contrast, serif length, crossbar height, or any of the granular details you might want to tune. A font foundry can add custom axes for these, and many do, but they are not part of the OpenType specification. That means a custom axis like 'XHGT' for x-height will work in the font editor and in clients that pass it through, but it will never animate, never map to a standard CSS property, and never behave consistently across platforms. If you need a specific typographic feature, choose a font that has an axis for it. Do not assume you can build one.
The practical takeaway: the five registered axes cover the 95 percent case. Weight, width, optical size, slant, and italic. Everything else is a bonus that may or may not work. When you inspect a variable font in the dev tools, look for the axes listed in the font's metadata. If the axis you need is not there, it does not exist in that font. This is not a failure of the format. It is the format being honest about what it can do.
Variable Font File Size: When the Savings Are Real and When They Are Not
The marketing says a single variable font file replaces a static family. The reality is more nuanced, and the difference matters when you are paying for bandwidth.
A variable font stores the entire design space, every weight, width, and optical size, in one file. In a static family, each weight is a separate file: Roboto Regular is one file, Roboto Bold is another, Roboto Light a third, Roboto Black a fourth. The variable font should, in theory, be smaller than the sum of all those statics. And it often is, if you are using three or more weights across a wide axis range. A variable font with a wght axis from 100 to 900 might ship at 150 KB in WOFF2, while five static weights at 40 KB each would total 200 KB. That is a real saving.
The Two-Weight Trap and the Subsetting Problem
But here is the trap. If your project uses only two weights, say regular and bold, the variable font file is often larger than the sum of the two static WOFF2 files. The variable file is 150 KB and the two static files are 30 KB each. The variable file is 2.5 times heavier, and the unused axis data, all those intermediate weights you will never call, is dead weight. The same logic applies to width and optical size. If you only need a single width and a single optical size, the variable font carries that data for no reason. The file size savings only materialise when the axis range is actually used.
There is also the question of subsetting. A variable font with a large glyph count, say 400 glyphs for Latin Extended, will be heavier than a subset that only includes the 200 glyphs your content needs. Static fonts can be subsetted aggressively. Variable fonts are harder to subset because the glyphs are tied to the axes. The result is that a variable font on a multilingual site can be bigger than the equivalent static family, unless you are willing to lose the fallback coverage.
The rule of thumb: use a variable font when you need three or more weights, two or more widths, or optical sizing. If you are shipping a marketing site with two weights and no other axes, stick with statics. The performance win you are chasing is not there.
Variable Font Support: Where It Works, Where It Fails, and How to Check
The support story for variable fonts is mostly good. The question is not whether variable fonts work. They do, in every current desktop client and in Safari on iOS 16.4 and later. The question is where they break, and the answer is in the operating systems and the CSS syntax.
Android and the System Font Gap
Android is the biggest gap. Android system fonts, the ones that ship with the OS, not the ones you load from a web font, do not support variable axes. If you rely on a system font stack that includes Roboto or Noto Sans, you get the static instance, not the variable one. This matters because a lot of variable font testing happens in Chrome on Android, where the variable font you loaded via @font-face will work, but the system fallback will not. The result is inconsistent rendering between the web font and the fallback, a compounding problem if your variable font fails to load and the system font takes over.
Safari's Parsing Strictness
Safari has its own quirks. The font-variation-settings property is supported, but the implementation has known parsing differences. Safari is stricter about the order of axis declarations and will silently ignore a declaration it does not understand. Write font-variation-settings: 'wght' 400, 'wdth' 100; and Safari may parse it correctly. Mix in a custom axis tag like 'XHGT' 0.5 and the whole declaration can fail. All text falls back to the default axis position. This is not a bug; it is the spec being applied literally. The fix is to declare the registered axes separately from custom ones, or to test in Safari before shipping.
The durable source for checking support is caniuse.com. It updates as clients ship, so the answer may have changed by the time you read this. As of the last build, the support table showed variable fonts in all desktop clients since 2018, with Android and Safari edge cases being the ones to watch. Before you commit to a variable font for a project, spend five minutes on caniuse.com and check the specific feature you need: font-variation-settings, font-optical-sizing, and the WOFF2 variable font format itself. The table tells you exactly which clients will work and which will silently fail.
font-variation-settings CSS: Syntax, Pitfalls, and What Happens When You Omit It
The font-variation-settings CSS property is how you control a variable font. It is a low-level property. You list the axis tag and the value you want, separated by a space, with multiple axes separated by commas. The syntax is font-variation-settings: 'wght' 400, 'wdth' 100;. You can also use the higher-level properties, font-weight, font-stretch, font-style, and the client will map them to the axes automatically. But the mapping is not perfect, and it is where the failures start.
Three Mistakes That Break Your Declaration
The most common mistake is omitting font-variation-settings entirely and assuming the font will pick up the weight from the CSS. Set font-weight: bold; and the variable font has a wght axis, the client maps bold to 700, and it works. But set font-weight: 600; and the font only has a wght axis, the client interpolates between the nearest instances, which may not be what you want. The higher-level properties are a convenience. They are not the same as explicit axis control. If you want a specific weight, write it down.
The second mistake is using wrong syntax. Some developers write font-variation-settings: wght 400; without quotes around the axis tag. That works in some clients but not others. The spec says the tag must be a string, so it should be quoted. Safari will reject an unquoted tag, and the whole declaration fails. The same applies to the order of axes: the spec does not require a specific order, but some clients are picky about it. Quote everything and list axes in alphabetical order. It is not required, but it is less likely to trip a parser.
The third mistake is forgetting that font-variation-settings is inherited. Set it on a parent element and every child inherits it. If you later want to reset it on a child, you have to explicitly set it to the regular values. This is a common source of confusion when a headline inherits a weight it should not have. The remedy is to be explicit: set font-variation-settings on every element where you need it, or use the higher-level properties and let the cascade handle it.
When you omit font-variation-settings entirely, the font renders at the default axis position. The regular weight, normal width, and default optical size. That is fine if you want regular text, but it means your bold headline is not bold. The client does not throw an error; it just uses the default. This is the silent failure that makes variable fonts look broken when they are not. The fix is always the same: declare the axes you need, test in the client, and check the console for parsing errors.
The Practicalities: Google Fonts Variable, WOFF2, and OpenType 1.8 in the Wild
The variable font ecosystem is now mature enough that you do not need to be a type engineer to use it, but you do need to understand the plumbing. Google Fonts has offered variable fonts since 2020 via its API v2, and the catalogue now holds more than 300 variable families, a figure that changes monthly, so check the Google Fonts site for the current number. The fonts are free and open-source, mostly under the SIL Open Font License, which means you can use them in web projects without paying a licensing fee. Note that the license has a copyleft clause for the font files themselves: if you modify the font, you must release the modified version under the same license. For most web use, loading the font as-is, this is not a concern.
WOFF2 and Self-Hosting
The WOFF2 format is the one you want for variable fonts on the web. WOFF2 supports variable font tables natively since the specification update in 2018, and it compresses better than its predecessor. Google Fonts serves WOFF2 by default, so you do not need to worry about fallbacks for modern clients. If you are self-hosting, ensure your server serves the correct MIME type and that you are using the format('woff2-variations') or format('woff2 supports variations') hint in your @font-face rule. Without it, some clients will not recognise the file as a variable font and will fall back to static rendering.
There is a performance consideration here that is often missed. A variable font in WOFF2 can be 50 to 300 KB, while four static weights might be 200 to 600 KB. The variable font is usually smaller, but only if you use the axes. If you only use the regular weight, you have paid for variable capability you are not using. The trick is to use the axes: define a font-weight range in your CSS, use font-optical-sizing for headings, and let the client interpolate. That is where the savings come from.
One more practical note: variable fonts are not a drop-in replacement for static fonts in every context. If you are generating a PDF with embedded fonts, or using a print workflow that uses PDF 2.0 (ISO 32000-2:2020), variable fonts are supported. But older print workflows, or any tool that does not understand OpenType 1.8, will treat the font as a static instance. Keep a static font family as a fallback for non-web contexts, at least until the tooling catches up.
Common Misconceptions: Custom Axes, Animations, and the Real Limits of the Format
When variable fonts were announced, the design community went a little overboard. The fantasy was a single font with a slider for every conceivable property: x-height, contrast, serif length, even 'wonkiness'. That is not how it works. The OpenType 1.8 specification registers five axes. Those are the only ones the client or the operating system is guaranteed to handle. Any other axis is custom, and custom axes are a compatibility risk.
Consider the font-variation-settings property again. It can pass through custom axes, but the client does not know what to do with them. Define an axis 'XHGT' for x-height and you have to write the CSS yourself. You also have to hope the font actually has that axis. Most fonts do not. The registered axes are the only ones with a defined meaning, and even they have limits. There is no registered axis for serif thickness, for crossbar height, or for the shape of a terminal. If you need those, you are outside the spec and you are on your own.
Animation: What Works and What Jumps
Animation is another area where the hype outstrips reality. You can animate the registered axes using CSS transitions or animations, transition: font-variation-settings 0.3s ease;, and it works in modern clients. But the performance varies. Animating the wght axis is smooth because the client can interpolate between instances. Animating a custom axis is not guaranteed to be smooth, and in some clients it will jump between steps. The limitation is not the format. It is the rendering engine's ability to calculate intermediate states. Test animations in the clients your users actually use, and keep them subtle.
The other misconception is that a variable font is automatically better for every project. It is not. A variable font with a large glyph count can be heavier than a subsetted static font, especially if you only need a few characters. It can also be slower to render on low-end devices, because the client has to calculate the outline for each axis combination. The file size savings are real, but they are conditional. They require you to use the axes, and they require the font to be well-made. A poorly constructed variable font, one with broken hinting or inconsistent axis ranges, will render worse than a well-made static font. The format is a tool, not a quality guarantee.
FAQ: Variable Fonts, Axes, and Support, Seven Questions That Cover the Rest
What is the difference between a variable font and a static font?
A static font is a single font file with a fixed weight, width, and style, for example, Roboto Regular or Roboto Bold. A variable font is a single file that contains an entire design space, allowing you to interpolate between different weights, widths, optical sizes, slants, and italics using the font-variation-settings property. The file size is larger than a single static font but smaller than the sum of multiple statics.
Which axes are supported by all variable fonts?
Only the five registered OpenType axes: wght (weight), wdth (width), opsz (optical size), slnt (slant), and ital (italic). These are defined in the OpenType 1.8 specification and are the only axes guaranteed to work across clients. Custom axes, such as x-height or contrast, are not registered and may not render everywhere.
How do I use font-variation-settings for multiple axes?
List each axis tag and value, separated by a comma, like this: font-variation-settings: 'wght' 400, 'wdth' 100;. Quote the axis tags, and separate multiple axes with commas. If you omit the property, the font renders at its default axis position. Put the property on every element where you need non-default values, as it inherits.
Is font-optical-sizing the same as the opsz axis?
Yes. The font-optical-sizing CSS property, with values auto or none, controls whether the client uses the optical size axis. When set to auto, the client picks the opsz value based on the font size. You can also set the opsz axis directly via font-variation-settings, but font-optical-sizing is the more convenient, higher-level way to get the same result.
What happens if a client does not support variable fonts?
If the client does not support variable fonts, it will ignore the font-variation-settings property and render the font at its default instance, the regular weight. The text will not be bold, condensed, or optically sized. To handle this, provide a static font family as a fallback in the @font-face rule, using the unicode-range descriptor to serve statics only to non-supporting clients. Check caniuse.com for current support.
Can I animate the weight axis for a hover effect?
Yes, you can animate the wght axis using a CSS transition on font-variation-settings. For example, a { transition: font-variation-settings 0.2s; } a:hover { font-variation-settings: 'wght' 700; }. This works in Chrome, Firefox, and Safari, but the animation may be janky in some mobile clients. Test on the target devices before shipping.
Why does my variable font render at default weight despite my CSS?
There are three common causes. First, you may have omitted font-variation-settings entirely or written it with wrong syntax, such as unquoted axis tags. Second, the font file may not have the axis you are trying to set. Check the font's metadata in a tool like FontLab or on the Google Fonts page. Third, the client may not support the axis, for example, a custom axis in Safari. Fix the declaration, verify the axis exists, and test in a supported client.
Who Should Use Variable Fonts, and Who Should Skip Them
Variable fonts suit the designer who needs fine control and is willing to test. They suit the team shipping a marketing site that uses five weights across headings and body text, because one file replaces four or five statics and the bandwidth savings are real. They suit the product designer building a design system where optical sizing matters, because the opsz axis adapts headings to display size without a separate font. They suit anyone working in a modern stack, Chrome, Firefox, Safari, Edge, who can afford to check caniuse.com and handle the Android system font fallback.
Variable fonts do not suit the designer who needs two weights and nothing else. The file is bigger, the setup is more complex, and the dead weight of unused axes is a cost you are not recovering. They do not suit projects targeting older clients or WebViews that do not support OpenType 1.8, because every user will see the regular weight and the page will look broken. They do not suit performance-critical mobile pages where every kilobyte counts, unless you are certain the axis usage justifies the file size. And they do not suit anyone who expects a variable font to fix bad typography. The format gives you control, not taste.
If you are still deciding, start small. Pick one variable font from Google Fonts, load it on a test page, and try the axes. Set a weight of 350 instead of 400, an optical size of 16 for body text and 32 for headings, and see if the difference is worth the complexity. Then measure the file size and compare it to the static equivalent. The evidence will tell you faster than any article whether the format is for you.
Meta: The One Sentence That Makes This Page Worth Reading
If you take only one thing from this page, let it be this: a variable font saves bytes only when you actually use the axes, if you ship a 150 KB variable file to display two static weights, you have wasted bandwidth and gained nothing, a mistake that is only revealed when you measure the network tab, not when you look at the design.