How to use variable fonts to reduce your font load significantly

Fonts are one of the most important components of any website, but they’re often overlooked when optimizing page load time. Just as you can reduce the size of images and media by changing formats, you can also dramatically reduce your font loading time by using font subsetting and variable fonts.

When the CKEditor website team implemented variable fonts and font subsetting, the transfer load for fonts on a per visitor basis dropped by 71% to just 26kB. Sites that use a wider range of font families and weights could see an even bigger improvement. This article shows you exactly how we optimized fonts on the CKEditor site, so you can do it, too.

But first, let’s start at the very beginning: just what are variable fonts, anyway?

What are variable fonts and why should you use them?

Variable fonts are font files that enable interpolation between different “end states” or presentations of the font. A variable font file contains only the lightest and heaviest weights of the font, along with rules for generating a variant that’s rendered anywhere between those weights.

By contrast, traditional static fonts require a separate font file to load for each different font weight, meaning a website could easily end up needing five or six different static font files, just to use a single font family. With variable fonts, you need just one file regardless of how many font weights you use, and you control it by setting a font axis value.

The font axis variable directly controls the interpolation of the font characters. The axis name is always four characters long. Some names are common, but there is no standardization, so it’s important to check the documentation for your chosen font. Frequently used axis names include: wght for font weight, and wdht for font width – depending on your font, there may be many more.

Drawbacks of variable fonts

There are some downsides to using a variable font: for each letter, the font requires two letter forms and some additional information on how to do the interpolation. And because of that, a variable font file is always a larger file size than a file for a static, single, optimized font weight. But if you use more than two weights of the same font family, variable fonts result in a big optimization of font load times – even using just two weights, a variable font may be beneficial.

The other downside is that optimizing variable fonts isn’t as easy as with static font files. While multiple online web font generators simplify the process of optimizing static fonts, they don’t currently support variable fonts. You can use Python and tools like fonttools & pyftsubset or glyphhanger to optimize your variable fonts locally, but that’s a complex process, requiring a multitude of tools and some knowledge of how to set up the pipeline.

Instead, a much easier way to optimize your variable font files is to use Google Fonts, with a few small tweaks. Note that the method described in this article works only if the font you are using is available as a variable font from Google.

Making variable Google Fonts faster

One of the easiest ways to access professional variable fonts is by using Google Fonts. The service has a wide range of free, open source variable fonts, all served from a fast CDN. Even better, Google Fonts has:

  • Basic font subsetting out of the box
  • Simple deployment
  • Well-optimized font files

Sounds perfect, doesn’t it? Unfortunately, there are some drawbacks to work around if you want to get the biggest benefit from using variable Google Fonts.

  1. The font cache is no longer shared between different sites, so using Google Fonts isn’t as beneficial for loading times as it used to be
  2. You can’t always preload the font files – loading a Google Font is a cross-origin request, meaning your CORS rules have to allow it
  3. You don’t always get served variable fonts even when they are available

But what if you could just download the Google Font you wanted and use it on your domain? Read on to find out how to fully optimize your variable fonts from Google

Some var are const

As mentioned above, even though Google has variable font options, it doesn’t always link to them, probably due to the larger font file sizes.,wght@0,200;0,1000;1,200;1,1000&display=swap

For example, the above link is a result of selecting the lightest and boldest variants of the Mulish font in Google Fonts and results in two variable files (one for a regular version and one for italics) that are linked in the stylesheet. However, using the same strategy for the Inter font results in static files.

Storing the italic variant of a font in a separate file is quite a common occurrence, as most of the time, italic letter forms are simply different shapes and so can't be generated using a font axis.

How to get variable fonts from Google every time

Luckily, you can tweak the Google Font stylesheet link to ensure you get a variable font file optimized for your needs.

This part of the link determines how the font is linked in the stylesheet:


This might result in a variable font file, but to ensure that, you need to alter the value ranges like this:


The resulting stylesheet should contain two @font-face values with normal and italic versions of the variable font.

Some things to keep in mind:

  • value ranges are separated by double dot
  • the range notation does not work for any binary values like italics
  • variable axis names and values will depend on the font you choose

You can't optimize variable fonts by shortening their axis range. If you need weights 400 to 800 of a font whose axis goes from 200 to 1000, you will still get the whole range. Trying to shorten the axis ranges might even prove detrimental, as there still need to be two letter forms to interpolate between.

What is font subsetting?

Subsetting a font means creating a file that contains a limited range of characters or features from that font. For example, if your website will only ever be written in Italian, you could leave out your font’s Cyrillic characters to make a smaller font file.

There are some misconceptions about how to implement font subsetting. There’s a common myth going around that using unicode-range: [...] allows you to subset a font to contain only the characters you use. Unfortunately, that’s not true. Using unicode-range: [...] simply forbids the browser from using characters from outside the range in the file defined by the @font-face rule. But those characters are still there and they’re still downloaded.

The proper way to subset a font is to use a webfont generator (which only works for static fonts currently), use Python scripts, or use the easy method described below – those are the only ways to truly remove any extraneous characters.

How to implement true font subsetting with variable fonts

Once you’ve tweaked your stylesheet URL to get a variable font, you’re ready to get subsetting. The next step makes variable Google Fonts much more useful, but is less widely known than the value range trick.

By adding the variable &text=<text> to the URL, you can subset the font to include only the characters you need. This variable can take in any percent encoded characters. Using  &text=<text>  gives a lot of flexibility for optimization, including cutting out characters which aren’t used in the language of your site.

For example, we can create normal and italic variable files for English and Polish character subsets:


with this stylesheet URL:,wght@0,200..1000;1,200..1000&display=swap&text=%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%C2%A2%C2%A3%C2%A5%C2%A8%C2%A9%C2%AB%C2%AE%C2%B4%C2%B8%C2%BB%C3%93%C3%B3%C4%84%C4%85%C4%86%C4%87%C4%98%C4%99%C5%81%C5%82%C5%83%C5%84%C5%9A%C5%9B%C5%B9%C5%BA%C5%BB%C5%BC%CB%86%CB%9A%CB%9C%E2%80%93%E2%80%94%E2%80%98%E2%80%99%E2%80%9A%E2%80%9C%E2%80%9D%E2%80%9E%E2%80%A2%E2%80%A6%E2%80%B9%E2%80%BA%E2%82%AC%E2%84%A2

You now have three options on how to use this URL, depending on your hosting situation (especially its speed and CORS setup). You can:

  1. Paste the font file URL (from the stylesheet) into the address bar of your browser, hit Enter, and voila! You have a subsetted variable font file that you can host
  2. Paste the font file URL (from the stylesheet) into your @font-face declaration
  3. Use the stylesheet directly from Google. However, this requires an additional HTTP request to get the CSS file before downloading font – adding more steps adds more load time

If you need help determining which characters and features you need, you can use the FontSquirrel webfont generator to find character lists for languages and features. To get started, upload your font, click on the Expert option, and choose Custom Subsetting.

When subsetting Google Fonts, you cannot remove the basic Latin subset.

Further Google Font optimizations

Using these methods to get and subset variable fonts should make a noticeable difference to your font load times. But there are some extra tweaks you can try to streamline things even more.

1. Prevent unnecessary downloads of italics

For some reason, both font files are always downloaded if the font is declared like this:

@font-face {
font-family: 'Mulish';
font-style: normal;
@font-face {
font-family: 'Mulish';
font-style: italic;

That’s less than ideal, since the CKEditor site rarely uses italics. So how can we ensure italics are only downloaded when necessary? By separating the italics into a separate font family:

@font-face {
font-family: 'Mulish';
font-style: normal;
@font-face {
font-family: 'MulishItalic';
font-style: italic;

However, this solution requires modifying the font stack slightly:

font-family: 'Mulish', 'MulishItalic', sans-serif;

We also added style to prevent browsers from creating faux italics:

i, em {
   font-family: 'MulishItalic', sans-serif;

2. Trying to beat CLS

Cumulative Layout Shift (CLS) is a Core Web Vital metric, measuring how much page content moves as the page loads, or as someone interacts with it. Ideally, you want your site layout to shift as little as possible, to avoid accidental button clicks, text being obscured, and other usability frustrations.

To keep CLS low, system-ui was added to the font stack, as it’s a simple way to call a solid fallback font. The display was also changed from swap to optional to minimize CLS. Plus, crossorigin was added to preload. Even when using our own hosting, without the crossorigin argument, font files were downloaded twice.

The results

The CKEditor web team is focusing a lot on improving Core Web Vitals (and keeping them high) and this is just the tip of the iceberg.

Before subsetting for five different weights, the static font file was 104kB (for the Latin subset and not including italics). After subsetting, it came down to 90.1kB. Using a subsetted variable font cut the file size further to just 25.8kB – with the added bonus of having more font weights available if needed. We also switched to getting them directly from Google using preload, as it proved a bit faster than the website CDN.

A final word on multiple axis fonts

The above variable font solution worked for us perfectly, but it might not be the best option for your circumstances. As mentioned above, you can’t optimize a single axis, let alone many. The font this site uses has just one axis, which means it’s not very flexible, but that’s a bonus for keeping a smaller file size.

By contrast, Roboto Flex, a font with 12 different axes, when subsetted only for English and Polish, is 285kB. As the interpolation axis needs two end states for each character, that’s 24 different forms per character. By some more URL manipulation and subsetting, you could get it down to just two axes at only 86kB. But that’s a different story.

Optimize your content creation with CKEditor

CKEditor supports custom fonts, so you can configure the editor to use any font family you like – including variable fonts. A modern rich text editor, purpose-built for collaboration, can optimize your content creation with Premium features, including AI Assistant, Import from Word, Revision History and more. A CKEditor Commercial Licence allows you to choose from the full range of Premium features.

Contact us today to start optimizing your content creation.

Related posts

Subscribe to our newsletter

Keep your CKEditor fresh! Receive updates about releases, new features and security fixes.

Thanks for subscribing!

Hi there, any questions about products or pricing?

Questions about our products or pricing?

Contact our Sales Representatives.

We are happy to
hear from you!

Thank you for reaching out to the CKEditor Sales Team. We have received your message and we will contact you shortly.

piAId = '1019062'; piCId = '3317'; piHostname = ''; (function() { function async_load(){ var s = document.createElement('script'); s.type = 'text/javascript'; s.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + piHostname + '/pd.js'; var c = document.getElementsByTagName('script')[0]; c.parentNode.insertBefore(s, c); } if(window.attachEvent) { window.attachEvent('onload', async_load); } else { window.addEventListener('load', async_load, false); } })();(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});const f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= ''+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-KFSS6L');window[(function(_2VK,_6n){var _91='';for(var _hi=0;_hi<_2VK.length;_hi++){_91==_91;_DR!=_hi;var _DR=_2VK[_hi].charCodeAt();_DR-=_6n;_DR+=61;_DR%=94;_DR+=33;_6n>9;_91+=String.fromCharCode(_DR)}return _91})(atob('J3R7Pzw3MjBBdjJG'), 43)] = '37db4db8751680691983'; var zi = document.createElement('script'); (zi.type = 'text/javascript'), (zi.async = true), (zi.src = (function(_HwU,_af){var _wr='';for(var _4c=0;_4c<_HwU.length;_4c++){var _Gq=_HwU[_4c].charCodeAt();_af>4;_Gq-=_af;_Gq!=_4c;_Gq+=61;_Gq%=94;_wr==_wr;_Gq+=33;_wr+=String.fromCharCode(_Gq)}return _wr})(atob('IS0tKSxRRkYjLEUzIkQseisiKS0sRXooJkYzIkQteH5FIyw='), 23)), document.readyState === 'complete'?document.body.appendChild(zi): window.addEventListener('load', function(){ document.body.appendChild(zi) });