Variable Fonts Demo and Explainer
The idea behind Variable Fonts is to reduced the number and weights of the fonts in a page by consolidating the number and size of the fonts you download. Instead of downloading 4 font faces like I normally do:
/* Regular font */
@font-face {
font-family: "notosans-regular";
src: url("../fonts/notosans-regular.woff2") format("woff2"),
url("../fonts/notosans-regular.woff") format("woff"),
url("../fonts/notosans-regular.ttf") format("truetype"),
font-weight: normal;
font-style: normal;
}
/* Bold font */
@font-face {
font-family: "notosans-regular";
src: url("../fonts/notosans-bold.woff2") format("woff2"),
url("../fonts/notosans-bold.woff") format("woff"),
url("../fonts/notosans-bold.ttf") format("truetype"),
font-weight: 700;
font-style: normal;
}
/* Italic and BoldItalic Fonts ommited for clarity */
I can download one or two fonts that will take care of all the site's typographical needs. Theoretically we could consolidate both fonts into one and have to deal with fewer files, fewer HTTP requests and fewer items to load using Font Face Observer
@font-face {
font-family: "roboto-vf";
src: url("type/RobotoUpright-VF.ttf") format("truetype");
/* format should be truetype-variations but it doesn't seem to be supported */
font-weight: normal;
font-style: normal;
font-display: fallback;
}
@font-face {
font-family: "roboto-vf";
src: url("type/RobotoItalic-VF.ttf") format("truetype");
/* format should be truetype-variations but it doesn't seem to be supported */
font-weight: normal;
font-style: italic;
font-display: fallback;
}
Technical Explanation
From a technical standpoint, Variable fonts (a part of a new vesion of the Open Type standard) introduces 5 predefined axes:
- wght: "Weight"
- wdth: "Width"
- opsz: "Optical size"
- ital: "Italic"
- slnt: "Slant"
We can use these axes using new extensions to CSS (
CSS Fonts Module, Level 4) and a new attribute. When all browsers fully support Variable Fonts we'll
be able to use new values for
font-weight
,
font-style
,
font-width
,
font-stretch
and the new
font-optical-sizing
rules to achieve much tighter control over the way
our fonts look in the page.
There is a lower-level to control Variable Font features directly:
font-variation-settings
. Using this attribute, we can call the axes, both custom and predefined, directly.
Getting our hands dirty
Before we start
One of the challenges I've faced when developing this page is that there is no uniform scale for fonts to use. Minimum and maximum values depend on the font and, unless the font developmer makes them available somehow, you will have to guess what those values are (it took me most of a day to figure out the values for the Roboto VF font I used for some code examples). This is a big pain in the ass but how to save ourselves from it?
I discovered TTX, a part of FontTools in my browsing today. This tool will generate an XML file from our TrueType and OpenType fonts that we can inspect in an editor to get the data we need. As a Python package the installation is simple:
pip install --upgrade fonttools
To generate a human readable XML file (yes, they exist), run the following command:
ttx path/to/font.ttf
This will generate a TTX file that you can open in your favorite editor to search for the information we want. We're looking for the fvar table
that will be represented by the <fvar> element. Part of the fvar table for the Decovar font looks like this:
<fvar>
<!-- Inline -->
<Axis>
<AxisTag>BLDA</AxisTag>
<Flags>0x0</Flags>
<MinValue>0.0</MinValue>
<DefaultValue>0.0</DefaultValue>
<MaxValue>1000.0</MaxValue>
<AxisNameID>256</AxisNameID>
</Axis>
<!-- Rounded Slab -->
<Axis>
<AxisTag>TRMC</AxisTag>
<Flags>0x0</Flags>
<MinValue>0.0</MinValue>
<DefaultValue>0.0</DefaultValue>
<MaxValue>1000.0</MaxValue>
<AxisNameID>258</AxisNameID>
</Axis>
Downloading and preparing the fonts
The first thing we need to do is to load the font that we want to work with. This process is no different than loading regular fonts. The following example shows how to load two instancess of Roboto Variable Font. One for regular text and one for italics.
@font-face {
font-family: "roboto-vf";
src: url("type/RobotoUpright-VF.ttf") format("truetype");
/* format should be truetype-variations but it doesn't seem to be supported */
font-weight: normal;
font-style: normal;
font-display: fallback;
}
@font-face {
font-family: "roboto-vf";
src: url("type/RobotoItalic-VF.ttf") format("truetype");
/* format should be truetype-variations but it doesn't seem to be supported */
font-weight: normal;
font-style: italic;
font-display: fallback;
}
I normally use Font Face Observer to make sure that my fonts loaded before I use them and provide a good experience without the jarring and potential disorientation fromm text flashes and, potentially, repositioning of the text when the final font loads.
The Javascript code to load both Roboto Variable Fonts is shown below. Using this script, it assumes that fontfaceobserver.js
has already been loadded in your page.
'use strict';
// Load the font(s)
var roboto = new FontFaceObserver('roboto-vf');
var robotoItalic = new FontFaceObserver('roboto-vf-italic')
// Capture a variable for the document element
var html = document.documentElement;
// Add 'fonts-loading' class
html.classList.add('fonts-loading');
//load fonts
Promise.all([
roboto.load(),
robotoItalic.load(),
])
.then(function () {
// If they load switch class to fonts-loaded and
// log success to console
html.classList.remove('fonts-loading');
html.classList.add('fonts-loaded');
console.log('All fonts have loaded.');
}).catch(function () {
// If they fail to load switch class to fonts-failed
// and log failure to console
html.classList.remove('fonts-loading');
html.classList.add('fonts-failed');
console.log('One or more fonts failed to load');
});
Now that we've downloaded the font and prepared it for use, we can explore how to use the new featurres.
Using the fonts
To acommodate the new features in Open Type, the CSS working group has added new values to existing features to match the predefined Axes on these fonts and their values. The table below shows the Open Type 1.8 predefined axes, the equivalent CSS Propperties and the values these properties can take.
Open Type Predefined Axis | CSS Equivalent Properties | CSS Values | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
wght: "Weight" |
font-weight |
|
||||||||||||||||||||
wdth: "Width" |
font-stretch |
Single values from either colum on the table below
|
||||||||||||||||||||
opsz: "Optical size" |
font-optical-sizing |
|
||||||||||||||||||||
ital: "Italic" |
font-style |
The values for the oblique angle range from -90 and 90. |
||||||||||||||||||||
slnt: "Slant" |
With this information we can start playing with Variable fonts and their new capabilities.
Using RobotoUpright-VF
and RobotoItalic-VF
we'll look at some things you can do with Variable Fonts.
Variable fonts work like any other web font. We can leveerage existing values for CSS typography properties that match the open type predefined axes like in the example below:
.stretched1 {
font-size: 3em;
font-stretch: 200%;
font-weight: 900;
}
That produces the following HTML code when we wrap the word analysis in <em> tags
Demo text for analysis and evaluation.
Lower Level Plumbing: font-variation-settings
Another alternative, useful when the font has a custom axis that is not covered by the predefined axes in the table above is font-variation-settings
. We can revisit the Roboto example using font-variation-settings, like so:
.stretched2{
font-size: 3em;
font-variation-settings: "wdth" 200, "wght" 200;
}
Demo text for analysis and evaluation.
This will gives us access to both the predefined axes and to any custom axis the font provides. If at all possible we shouldn't use this property unless we absolutely have to. According to the Fonts, Leve 4 specification:
When possible, authors should generally use the other properties related to font variations (such as font-optical-sizing) whenever possible and only use this property for special cases where its use is the only way of accessing a particular infrequently used font variation.
Low-level font variation settings control: the font-variation-settings property
Because we will be working primarily with custom axes we can use this. If we use predefined axes, we'll fall back to the existing attributes.
Using a more complex Variable Font: Amstelvar
AmstelvarAlpha-VF
is a more complex fonts with many custom axes and it exposes some interesting pieces of the font to the designer and developer. It is also an example of a font using custom axes (those wih uppercase names in the table below).
Axis name | Short name | Minimum value | Maximum value |
---|---|---|---|
Weight | wght | 38 | 250 |
Width | wdth | 60 | 402 |
Optical Sizing | opsz | 10 | 14 |
x opaque | XOPQ | 5 | 500 |
x transparent | XTRA | 42 | 402 |
y opaque | YOPQ | 4 | 85 |
lc y transparent | YTLC | 445 | 600 |
Serif height | YTSE | 0 | 48 |
Grade | GRAD | 25 | 250 |
The code below shows how to modify custom axes for a font that supports them. Amstelvar supports all the axes listed in the table and some additional ones; it also shows how to combine traditional CSS rules with the new possibilities offered by Variable Fonts.
.amstelvar1 {
font-family: "amstelvar-vf";
font-size: 3em;
hyphens: none;
line-height: 1.2;
font-variation-settings:
'cntr' 99.9998,
'grad' 176,
'opsz' 13.999,
'srfr' 34.998,
'wdth' 803.999,
'wght' 175.98,
'xhgt' 999.988;
}
This is a demo for the Amstelvar variable font using custom axes.
Just for Fun: Decovar
DecovarAlpha-VF
is a decoraative font that really shines when it comes to using and combining custom axes to creaate stunning effects in a single font. The custom axes for Decovar represent either extreme values on a single axis or a combination of multiple axes.
Axis name | Short name | Minimum Value | Maximum Value |
---|---|---|---|
Weight | wght | 0.0 | 1000.0 |
Inline | BLDA | ||
Shearded | TRMD | ||
Rounded Slab | TRMC | ||
Stripes | SKLD | ||
Worm Terminal | TRML | ||
Inline Skeleton | SKLA | ||
Open Inline Terminal | TRMF | ||
Inline Terminal | TRMK | ||
Worm | BLDB | ||
Weight | WMX2 | ||
Flared | TRMB | ||
Rounded | TRMA | ||
Worm Skeleton | SKLB | ||
Slab | TRMG | ||
Bifurcated | TRME |
.decoDemo1 {
font-family: "decovar-vf";
font-size: 5rem;
font-variation-settings: "BLDA" 900;
}
The quick fox jumped
Experiments to work with: Animation
Because these are font attributes we should be able to animate them. We'll use keyframes to animate multiple axes of the font. It works but we have to remember that the fonts are under development so the result may not be exactly what you are expecting... tweak the number until it works the way you want it.
.decoDemo2 {
font-family: "decovar-vf";
font-size: 5rem;
animation: deco-animation;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-direction: alternate;
}
@keyframes deco-animation {
0% { font-variation-settings: "BLDA" 0, "TRMF" 0; }
100% { font-variation-settings: "BLDA" 999, "TRMF" 600; }
}
The quick fox jumped
Moving Forward: Issues to be aware of
These are some of the things I've learned when working with variable fonts:
Things don't mean the same for all fonts.
There is no uniform value for custom axes and fonts are not required to support the same values in a given axis. For example, Amstelvar has different minimum while Decovar has multiple axes that all support values between 0 and 1000, so there's no real way to create a scale that works for every font. TTX helps solve the issue but it requires Python and an additional tool to get the information we need before we can work with fonts.
See the CSS WG Github Issue 573, 539 and 518 to get an idea of what the issues may be.
Be mindful in how you use Variable Font Features
Just to abuse the Spiderman quote: "With great power comes great responsibility". Even more so than regular fonts, variable fonts may impact your site's performance if you're not careful. Variable fonts, because they pack many potential combinations of axes, are larger than individual font faces we need to be careful with the number of fonts that we use.