This website uses a variable font
Around the end of 2021, this little corner of the web started using a variable font. Newsreader is a beautiful design from Production Type commissioned by Google Fonts, whose sources are available on GitHub under the Open Font license (OFL).
I'd been reading about variable fonts for a while, but had probably never actually typed up the CSS with my fingers for a project up to that point. Here are a few things I learned about self-hosting variable fonts in the process of freshening up the typography on here.
A first good thing to do is add all the necessary properties to the @font-face
declaration
Newsreader comes with two separate font files, one for roman and one for italics. Each file has two variation axes: the weight (wght
) and the optical size (opsz
).
My initial @font-face
declaration for the roman style was pretty straightforward, and replacing the old typeface worked well out of the box.
@font-face {
font-family: Newsreader;
font-style: normal;
src: url('/fonts/Newsreader.woff2') format('woff2-variations');
}
body {
font-family: Newsreader;
}
It became apparent rather soon, however, that this setup only works in Firefox. Things that were supposed to look bold — headings, <strong>
, and the like — didn't look as good in Safari and Chrome, as seen in this demo. Both had trouble, in different ways, rendering font-weight: 700
.
Safari produces what seems to be twice-bolded text by obtaining the appropriate bold weight from the variable font but then synthesizing a bolded version of that. In fact, disabling the browser's font synthesis with font-synthesis: none
fixes the issue. As for Chrome, whatever it's doing, it's not improved by this declaration.
What gives? wght
was supposed to be a registered variation axis that can be controlled via font-weight
but, as seen in the demo, only font-variant-settings: "wght" 700"
works consistently across browsers for bold text.
It was only obvious in retrospect that a webfont with a weight variation axis absolutely needs its weight range spelled out in the @font-face
declaration, for the axis to be correctly mapped to the font-weight
property. You do that with the @font-face/font-weight
property:
@font-face {
font-family: Newsreader;
font-style: normal;
font-weight: 200 800;
src: url('/fonts/Newsreader.woff2') format('woff2-variations');
}
Similar declarations are needed for other registered axes:
- the
slnt
axis which controls the angle of the oblique style needs a range defined with@font-face/font-style
- the
wdth
axis which controls the font width needs a range defined with@font-face/font-stretch
Adding the correct @font-face
properties enables declarations such as font-style: oblique 30deg
or font-width: 125%
to work as expected.
Variable fonts can also technically include the registered ital
axis to bundle both roman and italic styles in a single file. Using such a font in CSS is a bit more complicated, so as a consequence pretty much everyone bundles romans and italics in separate files.
A note on format()
in the src
descriptor
By the time I got to reading up on variable fonts, the syntax for @font-face/src
in the spec did no longer match the recommendations I'd found elsewhere for hinting to the browser that the url()
points to a variable font.
Where were format('woff2-variations')
and format('woff2 supports variations')
coming from? But, more importantly, how to usefully hint variable fonts today? I made a browser test to check support for the various syntaxes and came to these conclusions:
format('*-variations')
was the proposed syntax around the time when browsers adopted variable fonts, and the only syntax supported by browsers at the time of writing;format(woff2) tech(variations)
is the latest syntax. It separates the font format from font technology descriptor, as explained by Chris Lilley and Dominik Röttsches. This syntax is unsupported in current browsers and, if specified, must be done in a second@font-face/src
declaration.
With these findings in mind (which you can now find in the compatibility table on MDN), variable fonts using the WOFF2 format only really need the format('woff2-variations')
hint to ensure only browsers that have support for variations download the font file. This syntax works today and will most likely work indefinitely.
It's also probably a good idea to optimize variable web fonts (as long as you're allowed to)
Between the two variation axes (weight and optical size), Newsreader packs the information from nine separate source fonts. That adds up to a total of 455kb for the two WOFF2 files it ships. This is the overhead you have to pay for being able to produce as many fonts as you like, everywhere across the design-variation space.
This overhead is not always justified for web pages, so a common thing to do is split the font into many pieces across an aspect of the font. fontTools
is a collection of libraries and command-line tools for all sorts of font manipulation written in Python.
Richard Rutter has a guide on installing fonttools
on macOS and using it to subset variable fonts. One understanding of subsetting is to cherry-pick a set of Unicode ranges that cover all the glyphs your content needs and call it a day. A more resilient approach is to effectively split the font in a series of scripts („alphabets”). As long as you know the Unicode bounds of these scripts — and Paul Hebert reminds us that, in a pinch, you can grab some ranges from Google Fonts — this lets you serve the entire glyph set the font offers, and let the browser fetch whatever it needs to render the content of the web page.
Narrowing the variation space
In addition to glyph sets, there's another direction along which to subset a variable font. fonttools
comes with the varLib.instancer
module that allows you to create full instances (i.e. static fonts) from variable fonts, as well as 'partial' variable fonts that only contain a subset of the original variation space.
There is such a thing as variable lite. If, for example, you can make do with a single optical size, or you've decided wght: 376
is the perfect weight for body text, and you only need another bold weight to go with it, it might be worth it to either:
- produce a handful of static instances of the variable font, or
- restrict the design-variation space to just the region you need.
The command below pins the optical size at 12 while reducing the weight range to [300–700]
for the Newsreader roman font:
fonttools varLib.instancer ./Newsreader.woff2 wght=300:700 opsz=12 -o ./Newsreader-partial.woff2
This cuts the file size in half, from 215kb to 98kb. I've intentionally picked a "bad" example, because the reduction is foremost attributable to removing the optical size variation axis. If we omit the wght=300:700
option in our command we end up with mostly the same reduction (215kb to 102kb):
fonttools varLib.instancer ./Newsreader.woff2 opsz=12 -o ./Newsreader-partial.woff2
Recall that Newsreader uses font data from wght=200,400,800
, corresponding to the minimum, default, and maximum weight. To render the wght=300:700
range we still need data for the new minimum and maximum weight. To truly see a reduction in size, our weight range would have to be defined in such a way that at least one set of font data can be dropped from the variable font, for example wght=400:800
:
fonttools varLib.instancer ./Newsreader.woff2 wght=400:800 opsz=12 -o ./Newsreader-partial.woff2
Legal aspects of subsetting
Fonts are distributed under a variety of licenses that govern their use. The following apply to fonts distributed under the SIL Open Font License, as is the case for Newsreader. Obviously, I'm not a lawyer so this is just my layperson understanding of the matter.
- Converting a
.ttf
/.otf
file to a.woff
/.woff2
just to optimize its delivery across the network (WOFF is a compressed format), without any other alterations, preserves its Functional Equivalence, and is permitted under the OFL license; - Subsetting a font alters its functionality, so it produces a derivative work, which is subject to some rules.
When producing a derivative work you need to change the font's name to something completely different when and if the OFL license mentions a Reserved Font Name (RFN) as part of its copyright line. Newsreader has the following copyright line:
If the authors wanted Newsreader to be an RFN they would have mentioned that in the copyright line, and I would then have been compelled by the license to rename my subsets some cringeworthy pun like, I dunno, Oldswriter.
Anyways, just something to be aware of. And if I'm getting this wrong, I would appreciate a nudge!
Addenda: readings, tools, and resources
I can't help myself from turning every article into a little compendium, so here are some tangentials I've found useful.
OpenType Font Variations is part of the OpenType specification. John Hudson announced this addition to OpenType 1.8 with Introducing OpenType Variable Fonts, highly recommended reading.
A few guides to getting started:
- The Variable fonts guide on MDN
- A Variable Fonts Primer. Still from Jason Pamental, Variable Fonts: What Authors Need to Know and An Introduction to Variable Fonts for 24 Ways
- Introduction to variable fonts on the web on web.dev.
- The newly-inaugurated Google Fonts Knowledge covers variable fonts.
- Zach Leatherman on developing a strategy for loading fonts on CSS tricks.
An excellent tool for inspecting variable fonts (and, really, any kind of webfont) is Wakamai Fondue, available on the web and at the command line.
Finally, a couple of fun facts that surfaced while researching this article:
- Chris Coyier: Actually, the San Francisco Typeface Does Ship as a Variable Font.
- Firefox is the only browser that uses
b, strong { font-weight: bolder }
in its user agent stylesheet. Šime Vidas points out how this can be a problem with variable fonts. - the largest Unicode code point is
U+10FFFF
.