The joy of Variable Fonts: getting started on the Frontend

Cover for The joy of Variable Fonts: getting started on the Frontend

Topics

In this article, we’ll take a deep look at getting set up with variable fonts on the frontend, look at properly loading them, deal with settings, look at some ‘gotchas’ and more—read and learn!

Other parts:

  1. Variable fonts in real life: how to use and love them
  2. The joy of Variable Fonts: getting started on the Frontend

In our last article describing why you should use variable fonts and examining some real-life use cases, we learned to love variable fonts (check it out!). This time around, we’re ready for that love to blossom into a beautiful new joy. This post is for web developers who are ready to take the variable font plunge, although designers (and the curious in general) are certainly welcome to read, too!

A conceptual, abstract image corresponding to loading variable fonts versus static fonts

Loading variable fonts vs. static fonts

Although the process of loading variable fonts via the @font-face directive is pretty similar to loading static fonts in the same way, there are still some slight differences that should be mentioned. This applies both to how we refer to the source file, and set up style ranges, and how we deal with default styles. Let’s discuss all of these.

How to specify source file formats

First, for the sake of comparison, let’s take a look at a basic example demonstrating how to load a static font in CSS:

@font-face {
  src: url('/fonts/MartianGrotesk.woff2') format('woff2');
}

Now, with variable fonts, across various specifications you can find a number of different loading approaches—but which is the correct method? Let’s try to use the same format as with the static font, but we’ll just change the file to the variable version (note the VF in the filename):

@font-face {
  src: url('/fonts/MartianGroteskVF.woff2') format('woff2');
}

Will this work? The simple answer is, yes, it will. But the problem with this approach is that we aren’t providing any indication to the browser that this font is a variable font. This means instead of gracefully falling back, it will load a font which it doesn’t actually know how to use.

There are a couple of ways to specify variable fonts, although some of these are now deprecated, or lack widespread browser support, so it’s a good idea to recognize them if you see them in the wild. Let’s examine three methods, then we’ll reveal our solution.

Indicating variable fonts

First, we could provide an indication that we’re using a variable font with the z*-variations format:

@font-face {
  src: url('/fonts/MartianGrotesk-VF.woff2') format('woff2-variations');
}

The z*-variations format was the first standard for solving this problem, and most browsers offer support for this method. The problem is it’s deprecated in the most up-to-date specifications.

Second, in a lot of recent articles you might’ve encountered the supports variations format:

@font-face {
  src: url('/fonts/MartianGrotesk-VF.woff2') format('woff2 supports variations');
}

Although the above format was featured in last year’s W3C drafts, they’ve already decided to change it—so there’s no reason to use it.

Finally, according to the latest specification draft, this is how it should be done instead:

@font-face {
  src: url('/fonts/MartianGrotesk-VF.woff2') format('woff2') tech('variations');
}

But don’t rush too fast implementing this type of declaration! If you try to make the switch it now, you’d probably just break the entire @font-face directive due to a lack of widespread support.

TLDR: Which format should I use?

We probably shouldn’t rely on formats that have been deprecated or which aren’t still widely accepted. But, there is one thing that we can use: feature queries.

We can target browsers via the @supports directive and load our variable fonts only if they support them. And, actually, @supports has wider support than format('woff2-variations')!

Plus, when a new standard becomes widely available, we can easily add tech('variations') and any additional features that new standard might bring with it, and our @supports directives will probably still be pretty useful since older browsers could have trouble parsing the new format, too.

@font-face {
  font-family: Martian Grotesk;
  src: url('/fonts/MartianGrotesk.woff2') format('woff2');
}

@font-face {
  font-family: MartianGroteskVF;
  src: url('/fonts/MartianGroteskVF.woff2') format('woff2');
}

body {
  font-family: MartianGrotesk;
}

@supports (font-variation-settings: normal) {
  font-family: MartianGroteskVF;
}

Using ranges instead of single values

Another difference when loading variable fonts is that we set font styles, not as single values, but as ranges.

There are two properties that require ranges: font-weight and font-stretch, and, if your variable font supports them, they must be declared. If you do not explicitly add these style ranges to variable font declaration there could be unpredictable results. For instance, some browsers might try to get these metrics from the font, but some will try to use the default ones. In any case, it’s always better declare them explicitly.

Let’s compare how we’d add two files to correspond to Regular and Bold styles with how we’d do the same thing with a singular variable font style using range values.

First up, the static font version using numbers and two files:

@font-face {
  font-family: MartianGrotesk;
  src: url('/fonts/MartianGrotesk.woff2') format('woff2');
  font-weight: 400; /* normal */
}

@font-face {
  font-family: MartianGrotesk;
  src: url('/fonts/MartianGrotesk-bold.woff2') format('woff2');
  font-weight: 700; /* bold */
}

How let’s see the variable font version:

@font-face {
  font-family: MartianGroteskVF;
  src: url('/fonts/MartianGroteskVF.woff2') format('woff2');
  font-weight: 100 1000;
  font-stretch: 75% 200%;
}

So, where to get these font metrics? Well, your font probably has some kind of documentation, so that would be a great place to start!

…But let’s imagine we’re dealing with a font that doesn’t have documentation.

In these cases, you could always can use services like Dinamo Font Gauntlet or Wakamai Fondue to find out everything possible related to a variable font’s settings.

Default font settings

There are 5 main axes and endless possibilities for custom ones. We’re not going to dig too much into this, mostly there are many variables and this can depend on the details of a particular font. But what should we do if we want to provide our font with some default customization?

It’s possible to use the font-variation-settings property inside @font-face directive to apply default settings to variable fonts:

@font-face {
  font-family: MartianGroteskVF;
  src: url('/fonts/MartianGroteskVF.woff2') format('woff2');
  font-weight: 100 1000;
  font-stretch: 75% 200%;
  font-variation-settings: 'wdth' 100, 'wght' 400;
}

Let’s talk about different properties: font-variation-settings is a generic, low-level property, while high-level properties include separated, specific properties like font-weight, font-stretch, and so on.

Be careful with the units that you use with these. You might notice that font-variation-settings uses numbers for each property. This is because font-variation-settings is a low-level property. With high-level properties, such as font-stretch, font-style, etc., this may differ. For instance, font-stretch uses percentages, and if you use font-stretch without a percent it won’t work.

A conceptual, abstract image corresponding to using variable fonts in CSS

Using variable fonts in CSS

So, we’ve just declared and loaded our bright and shiny variable fonts, what to do next? We need to figure out how to adjust our styles and which properties we’ll use to do that. We now have great power in our hands with a huge (yet finite) amount of variations.

This level of possibility can pose a number of practical questions, case and point: if we want to have an ultra-bold and ultra-wide font, should we use font-weight or font-variation-settings?

This is on the menu:

h1 {
  font-weight: 1000;
  font-stretch: 200%;
}

But so is this:

h1 {
  font-variation-settings: 'wdth' 200, 'wght' 1000;
}

While you might feel that having one property to rule them all (font-variation-settings) is a good choice, actually, not so much. Primarily, font-variation-settings is a low-level property that should mostly be used to manipulate custom axes which (obviously) don’t have their own CSS properties.

The low-level font-variation-settings only works for variable fonts and would not have the same behavior if variable fonts are not supported by the browser and a fallback font is used. So, we recommend using high-level properties as they can predictably work with your regular/fallback font.

Instead, the golden rule is to use high-level properties (font-weight, font-stretch, font-style) whenever possible.

Keep font-variation-settings for custom use, or if you need to configure optical-sizing with a value (since the font-optical-sizing property doesn’t support number values yet, with only support for auto/none at the moment).

Further, there is another downside of using font-variation-settings—the corresponding properties will not be inherited if another font-variation-settings has been applied to any children, and will just be overwritten instead.

To illustrate a bit, in the example below, our <span> content will get the styles as assigned, but will fail to inherit the properties assigned to the parent <p> element:

<style>
p {
  font-variation-settings: 'wght' 1000;
}

span {
  font-variation-settings: 'wdth' 200;
}
</style>

<p>Hello, <span>world</span></p>

With modern CSS it’s possible to implement an inheritance mechanism via custom properties. (Although, we think it’s just not worth it unless it’s necessary to make custom axes inheritable.) Still, it would look something like this:

<style>
body {
  font-variation-settings: 'wght' var(--wght, 400), 'wdth' var(--wdth, 100);
}

p {
  --wght: 1000;
}

span {
  --wdth: 200;
}
</style>

<p>Hello, <span>world</span></p>

If we’re going to implement a fallback, it would be convenient to use properties that also apply to static fonts:

@font-face {
  font-family: Martian Grotesk;
  src: url('/fonts/MartianGrotesk.woff2') format('woff2');
}

@font-face {
  font-family: MartianGroteskVF;
  src: url('/fonts/MartianGroteskVF.woff2') format('woff2');
}

body {
  font-family: MartianGrotesk, sans-serif;
}

@supports (font-variation-settings: normal) {
  body {
    font-family: MartianGroteskVF, sans-serif;
  }
}

h1 {
  font-weight: 700;
  font-variation-settings: 'GRAD' 1;
}

In this case, using progressive enhancement font-weight would be applied to both the static and variable fonts, while GRAD axis would be used only for the variable font. If we had only used font-variation-settings, we would have had to provide some feature-query fallback; browsers which load static fonts will not recognize font-variation-settings.

A conceptual, abstract image corresponding to miscellaneous variable font mindfulness.

Miscellaneous Variable Font mindfulness

With that basic setup out of the way, let’s take a look at some other things that frontend developers should keep in mind when employing variable font goodness.

Why to avoid keyword values with high-level font properties

Also, let’s take a second to note CSS keywords, as these do exist for some high-level font properties. Still, I wouldn’t recommend using these because, although, they have default mappings according to specification, there is no guarantee that a font will follow this specification. Some fonts will change these metrics and steps to have more steps.

It’s always better to be explicit and use numeric values instead of keywords.

That being said, sure, you can check that everything works as it should and use them.

But also, be mindful that some user agents have predefined styles for tags. For instance, in Chrome, the <strong> tag has font-weight: bold which maps to font-weight: 700 and your variable font metrics may differ from those values. To solve this, you can use some CSS-reset or just redefine these to the desired values.

Unexpected behavior with -webkit-text-stroke

Variable fonts provide enormous flexibility, yet, over the course of some extensive use we’ve found one property which does not work as expected.

At times, you would want to customize a font so that it is outlined, and there’s a property to do this: -webkit-text-stroke. Although this property has a (scary) prefix, it’s widely supported by all major browsers.

But its behavior with variable fonts differs. You may notice that in addition to outlining the letters themselves, with a variable font, it also outlines all the individual parts that this letter consists of.

Path overlaps: variable vs. static

And this fact, that each letter consists of multiple parts, is necessary for font face interpolation.

We don’t think we would call this a downside, exactly, but still—be mindful.

Wrapping things up

In conclusion, we’re very pleased to finally breathe a sigh of relief—all the richness of typography is now available on the web, and the future is finally here!

We believe variable fonts are a huge breakthrough for the entire font industry. There are benefits for everyone—for users and for developers. All that’s left is for designers and frontend engineers to try them out and start using variable fonts in new projects!

And one more thing: if you’ve got a problem or project that needs help, whether it’s design-related or not, Evil Martians are ready to face any challenge! Get in touch!

Join our email newsletter

Get all the new posts delivered directly to your inbox. Unsubscribe anytime.