Boiling eggs and fixing the variable font inheritance problem
For this post we need the following ingredients:
- An imaginary webpage that explains how to boil an egg.
- An imaginary variable font called Cackleberry. It has two axis: weight (
wght) and slant (
slnt). The weight axis goes from 100 to 900, and has a default of 100. Slant goes from 0 to -14, and has a default of 0.
With this mise en place out of the way, let’s dive in!
Variable fonts in CSS
To work with variable font axes in CSS, you use the
font-variation-settings property. For instance, to set the weight axis to 666 for everything on the page, you’d write something like this:
This trickles down to every element on the page, like you’d expect:
All the text inside the
p, and the
li elements now have a font weight of 666 instead of the default 100.
So far, so good! Look at this beaut:
The inheritance problem
Now, a common culinary cock-up in boiling an egg is that people forget to put water in the pot. Since the presence of water is vital to boiling, let’s stress this fact:
Nice! The “with water” part is now wrapped in
em tags. Let’s make the font super italic-y by setting the slant axis (
slnt) to the maximum of -14 instead of the default 0:
Let’s refresh and admire our work:
Oh no! What happened to the weight of 666 inside our
em tags?! It’s gone, reset back to the default of 100. The slant is correct, but the font is all skinny!
We stumbled upon the inheritance problem.
What’s going on then?
The problem is this: whenever setting a value for
font-variation-settings, the values you don’t set get reset back to their defaults. Because we don’t set the
wght to 666 in our
em tag, the browser will use the default of 100 when rendering font.
In effect, we have created this CSS:
Okay, that’s annoying, but easy to fix. In this case, we could just set the proper
slnt for our
Problem solved, for now:
Our current CSS is small and simple. But still we already have a duplicated value. The more variations of our font we’ll add, the more exceptions and combinations we’ll have to write. This’ll turn into maintenance hell really quickly!
You know what cascades really well, though? CSS custom properties, also known as CSS variables! What if instead of setting
font-features-settings directly, we use CSS variables?
Let’s create a basic setup:
What’s going on here? Let’s parse this CSS:
First we create unique variables for each axis, and set them to their default values. We do this on the
:root element so these values will trickle down to every other element on the page.
We then apply these CSS variables to the axes of Cackleberry. All axes, at the same time! We do this on the
* element, so it will be applied to every element on the page. If Cackleberry gets applied to that element too, it will get the proper
font-variation-settings, and render as intended.
We now arrive at our
body styles. Since
body inherits from
:root, it will get all of
:root’s CSS applied as well. So in effect the CSS for
--cackleberry-wght: 666 is applied to
body directly, it overrides the value we inherit from
:root. This means that the values fed to the
font-variation-settings rule are now:
"wght" 666, "slnt" 0.
So far so good. We’re now at the same point of our first example: the entire page gets rendered in Cackleberry in
But here’s where it gets interesting, the styles for the
Again, instead of setting
font-variation-settings directly, we set our custom CSS variable for the
slnt axis. The other values cascade down from above, and are not affected. That’s significant! Because if we would’ve set
font-variation-settings, they would have been affected by being reset back to their defaults.
Effectively, the CSS for
em now looks like this:
So when that
font-variation-settings rule gets applied to
em, the values will be equivalent to this:
And that’s what we want! Set one value and leave the others at their inherited value, instead of resetting them. No more hard-coded
font-variation-settings values for every single element!
Works for OpenType features too
This approach works for
font-feature-settings too, the CSS property to tweak OpenType features:
In this example, small caps (
smcp) and stylistic set #1 (
ss01) are now individually turn-on-and-off-able, without fear of resetting a previously set OpenType feature. If you want to turn on
ss01 and leave
smcp at whatever it was, you can do:
If you’re using Wakamai Fondue to inspect your fonts, you might have already seen this trick in use:
Wakamai Fondue analyses any font you drop in the site, and spits out all the CSS you need to tweak variable axes and OpenType features, using the CSS variable hack.
Note that Wakamai Fondue uses a slightly different approach than our examples, by generating classes for each feature. You can just add the class for the feature you want to any HTML element, and the CSS will do the rest:
To better explain what’s going on, Wakamai Fondue also puts the feature name (
smcp in this example) in the variable, instead of just
The effect is the same: this specific feature/axis is set, the rest is left at their inherited value, instead of being overwritten. Any element with the class
cackleberry-smcp will get small caps. Great!
Registered axes and font-variant
To demonstrate this trick I’ve been using the
slnt axes here, but these registered axes are really meant to be set through
font-style. Same goes for the small caps example up here: these are best set with
font-variant-caps. Only when this isn’t supported you should fall back to low-level properties like
Eggs are done!
I think this is a nice way to circumvent the inheritance problem with
font-feature-settings. It works in every browser were variable fonts are supported, as they’ll also support CSS variables.
Some care should be taken when you have multiple variable fonts, as you probably want to be a bit more specific with which elements get the variable-infused
font-variation-settings rule applied.
I hope this trick helps you make awesome variable font sites! And perfectly boiled eggs.