Variable Fonts and Cross-Browser Compatibility
Learn how to load variable fonts reliably across browsers, declare supported axes, choose the right CSS controls, and troubleshoot rendering differences.
Variable fonts work across browsers when the font file, its declared axis ranges, and your CSS requests agree. Register the file with @font-face, use standard CSS properties such as font-weight and font-stretch for registered axes, and keep a conventional fallback family. Test the browsers and operating systems you support: support for one variable-font feature does not guarantee identical support for every descriptor or rendering detail.
A variable font is still a web font resource. One file can contain a range of weights, widths, or styles, but the font determines which axes exist and which values are valid. Check the font’s metadata before declaring ranges or writing axis settings.
1. How variable fonts work
A conventional font family often supplies separate files for styles such as regular, bold, and italic. A variable font can package one or more ranges of variation in a single file. CSS requests a point within those ranges, and the browser uses the font’s axes to render the text.
Common registered axes include:
wght: weight, controlled withfont-weight.wdth: width, controlled withfont-stretch.italandslnt: italic or slant variation, controlled withfont-style.opsz: optical sizing, controlled withfont-optical-sizing.
These are common mappings, not a promise that every font contains every axis. The font designer sets the available axes and their ranges. For the underlying CSS model, see MDN’s variable fonts guide.
2. Inspect the font before writing CSS
Start with the font’s specimen, documentation, or font metadata. Record the exact axis tags and minimum and maximum values. Confirm whether it has a variable italic face, a separate italic file, or no italic variation. Do not assume that a weight range such as 300–900 or a width range such as 75%–125% applies to your chosen font.
Also confirm that the file is intended for web delivery and that the URL serves the expected font. WOFF2 is generally a good web format because it compresses more efficiently than older WOFF or OpenType formats; see MDN’s @font-face reference.
3. Register the font and use standard CSS properties
Replace the example URL and ranges below with the values supported by your font. The ranges shown are illustrative, not universal.
@font-face {
font-family: "Example VF";
src: url("/fonts/example-vf.woff2") format("woff2");
font-weight: 300 900; /* Use the font's actual wght range. */
font-stretch: 75% 125%; /* Declare only if the font supports wdth. */
font-style: normal;
font-display: swap;
}
:root {
--font-body: "Example VF", Arial, sans-serif;
}
body {
font-family: var(--font-body);
font-weight: 400;
}
h1 {
font-weight: 760;
font-stretch: 95%;
font-optical-sizing: auto;
}
@supports (font-variation-settings: "wdth" 115) {
.compact-heading {
font-stretch: 85%;
}
}
The two-value font-weight and font-stretch descriptors tell the browser that the face supports a range. Declare only descriptors that match the font. MDN documents the range form for the font-weight descriptor.
font-display: swap allows fallback text to appear while the font loads, then replaces it when the font is ready. If matching text metrics matter during the swap, choose and tune a fallback carefully; the fallback will not reproduce the variable font’s design exactly.
4. Choose high-level properties or low-level axis controls
Use the high-level CSS property when the axis has a standard mapping. For weight, use font-weight; for width, use font-stretch; for italic or slant, use font-style; and for optical sizing, use font-optical-sizing. These properties communicate intent and are generally easier to maintain.
Use font-variation-settings for a custom axis or a low-level control without a standard CSS property. Axis tags are four-character strings and case-sensitive. Registered tags are conventionally lowercase, while custom tags are often uppercase; the font defines the tags it accepts.
.display-title {
/* Prefer font-weight when controlling the registered wght axis. */
font-weight: 780;
}
.experimental-heading {
/* Example only: replace "XTRA" and 400 with an axis and value
that the font actually supports. */
font-variation-settings: "XTRA" 400;
}
Low-level settings can override a matching high-level property, so avoid setting the same registered axis through both mechanisms unless that precedence is deliberate. See MDN’s font-variation-settings reference.
5. Handle browser and operating-system differences
MDN describes the font-variation-settings property as widely available since September 2018, but the @font-face variation-settings descriptor has limited availability. These are different capabilities. Avoid treating general variable-font support as proof that every descriptor or axis behaves identically. The operating system and font rendering stack can also affect the result.
Use a fallback family so text remains readable if the font fails to load. Where an enhancement needs gating, put it inside a feature query and leave the basic typography outside it. A feature query checks whether the browser recognizes a CSS declaration; it does not verify that the font file contains the requested axis or that the requested value is within its range.
There is no single browser-version number that guarantees every variable-font behavior. Set a support matrix based on your audience, then test the actual browser and operating-system combinations you deploy to. The reviewed sources do not provide a current version-by-version compatibility table.
6. Test the rendered result
- Load the page with the font request visible in the browser’s network tools. Confirm that the request succeeds and returns the intended WOFF2 file.
- Inspect computed styles for the text element. Check the final
font-family,font-weight,font-stretch, andfont-style. - Compare the same test page in each supported browser and operating system. Include both the base style and the extremes of each declared range.
- Test with the font blocked or offline to confirm the fallback remains legible and the layout still works.
- Check responsive sizes and real content. A width or weight that looks good in a specimen may alter line wrapping in a page layout.
When a visual difference appears, first determine whether the intended font loaded. A fallback can make axis changes appear ineffective even when the CSS itself is valid.
7. Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Weight or width does not change | The font lacks the axis, the requested value is outside its range, or the face range was declared incorrectly. | Inspect the font’s axes and metadata. Declare its actual range in @font-face and request a value within it. |
| The browser shows a fallback font | The font URL failed, the response was not the expected file, or the family name does not match. | Check the network request and console. Verify the URL, server response, file, and exact font-family name. |
| A custom axis is ignored | The tag is misspelled, has the wrong case, is not four characters, or is not supported by the font. | Copy the exact tag from the font’s documentation or metadata and use a valid value. |
| An axis works until another rule is added | A later declaration wins in the cascade, or a font shorthand resets font longhands. |
Inspect computed styles and declaration order. Put the intended longhand after the shorthand where necessary. MDN documents that the font shorthand resets font-variation-settings to normal. |
| High-level and low-level axis values conflict | font-variation-settings overrides the corresponding high-level property. |
Use one control for a registered axis, preferably its high-level CSS property, unless you need the lower-level override. |
| One platform looks different | The browser may be using a different font or rendering stack, or support for a specific descriptor may differ. | Confirm the same font loaded on both platforms and test the exact browser and operating-system pair. Keep the fallback and enhancement query in place. |
| Text shifts when the web font loads | The fallback and web font have different metrics. | Choose a closer fallback, review the layout at likely line breaks, and decide whether the visual swap is acceptable for the page. |
For shorthand behavior, consult MDN’s font reference. For the JavaScript FontFace API’s variation settings, see FontFace variationSettings.
8. Performance, reliability, and cost
A variable font can replace several static font files when the design needs multiple values on its supported axes. Whether that reduces the page’s total transfer size depends on the actual files and which styles the site uses. Compare the chosen variable file with the static files you would otherwise ship, and avoid loading font resources the page does not need.
font-display: swap keeps text available while the font loads, but the fallback may have different metrics. A failed request should still leave usable text through the fallback family. Test both the normal load and the unavailable-font case.
There is no runtime API charge for CSS variable-font styling itself. Costs may come from licensing, hosting, or bandwidth for the font asset; check the font’s license and your hosting arrangement. Do not assume one variable file is always smaller or faster than every set of static files.
9. Capture a visual check of the page
For a repeatable visual check, capture the same test page at the same viewport and compare the heading and body text across environments. A screenshot can help spot wrapping or fallback changes, but it does not identify which font loaded; check network and computed styles as well.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/font-test -o shot.webp
See the ScreenshotNeo API documentation for request options. You can also use its Python or Node.js request patterns:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/font-test"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/font-test'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
The Node.js snippet uses Bun’s file-writing helper to save the response. In Node.js, replace the last line with your preferred file-writing method, such as writeFile from node:fs/promises. ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo.
10. Or skip the browser setup
For a quick visual capture, send one request to ScreenshotNeo:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/font-test -o shot.webp
See the API documentation for the Python, Node.js, and other request options. ScreenshotNeo accepts cookie banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Get 1,000 free screenshots a month with no card.
FAQ
Can I use a variable font without a feature query?
Yes. A feature query is useful for gating an enhancement, but a fallback family and accurate font-face declarations are the core safeguards. Use a feature query when the target support matrix calls for it.
Should I set every axis with font-variation-settings?
No. Use standard properties for registered axes when available. Reserve the low-level property for custom axes or controls without a standard property.
Does one variable font file guarantee the same appearance everywhere?
No. The font’s available axes, CSS support, operating system, and rendering stack all matter. Test the combinations your site supports.
Can a screenshot confirm that the correct font rendered?
A screenshot shows the visible result, but it cannot establish which font file loaded. Pair visual comparison with network inspection and computed-style checks.


