Getting started with CSS Font Loading

I was in the mood to learn something new and so I decided to take a look at the CSS Font Loading API.

You may ask yourself why I chose CSS Font Loading. Well, because up until now I hadn’t taken the time and browser support is already pretty great. At least for me it’s good enough because I treat web fonts as an enhancement and I’m fine with older or less capable browsers serving system fonts.

Before I show you how it works, I want to really quickly review the state of web font loading and explain why we need something like CSS Font Loading. If you are not into reading (at the moment), here’s a CodePen for you.

Disclaimer: This article isn’t meant to be a deep dive into the API, but a quick start and a glance at some of the features.

The problem with @font-face

Before CSS Font Loading there was only one way for us to natively load and embed webfonts, the @font-face rule and the font-family property.

The good thing about @font-face is that fonts are loaded lazily, but the bad thing about @font-face is that fonts are loaded lazily.

Let me explain that: When you add a @font-face rule to your CSS in almost all browsers (IE8 is an exception) pretty much nothing happens [1]. Not until you declare this font somewhere in your CSS with font-family. That's good because fonts don't get downloaded needlessly if they aren't in use. What's bad about it is that we can't control what happens between the initial request and the font being rendered. We give it up to browsers completely and they behave quite differently [2]. This is where CSS Font Loading comes into play.

How CSS Font Loading works

The CSS Font Loading API gives us the ability to add fonts, track their download progress and handle lazy loading the way we want it. If you have used Bram Steins Font Face Observer before, this will be very familiar to you.

Defining and adding FontFaces
It’s pretty much like defining a @font-face rule. The first argument is the family, the second the source(s) and the third optional descriptors (style, weight, stretch, unicodeRange, variant, featureSettings).

var notoSansRegular = new FontFace('Noto Sans Regular', 'url(/fonts/NotoSans-Regular.woff2)', {
style: 'normal',
weight: '400'
});

If you want to use the FontFace, you have to add it to the so called FontFaceSet which is available as document.fonts[3].

document.fonts.add(notoSansRegular);

Loading a FontFace
After defining and adding a font, you can load it whenever you want and/or need it.

notoSansRegular.load();

Of course, you can define, add and load multiple FontFaces.

Tracking the status of the FontFace and the FontFaceSet
Declaring and adding FontFaces was easy. The fun part is tracking their status because this is where we can decide on the what, when and how.

[FontFace].status returns the current status (unloaded, loading, loaded or error);

console.log(notoSansRegular.status);

As soon as the font is loaded and ready, the loaded Promise resolves.

notoSansRegular.loaded.then((fontFace) => {
// This is where you can declare a new font-family, because the font is now loaded and ready.
console.info('Current status', fontFace.status);
console.log(fontFace.family, 'loaded successfully.');
// Throw an error if loading wasn't successful
}, (fontFace) => {
console.error('Current status', notoSansRegular.status);
});

loaded will only tell us when a single given font is loaded and ready, but we might want to be informed when all fonts are ready, if there are multiple. The earlier mentioned FontFaceSet has the ready attribute, which contains a Promise that resolves when the document is done loading all fonts.

document.fonts.ready.then((fontFaceSet) => {
console.log(document.fonts.size, 'FontFaces loaded.');
});

Easy cheesy, right? As soon as the fonts are ready, you can apply the font-family via JavaScript or add a class, that does that for you, or do whatever you want. You can see all of that in action in this CodePen. Of course, there are more things you can do with the API, but in most cases you'll be fine with loading fonts and knowing when they are ready.

Browser support and closing thoughts
Browsers that don’t understand this technique will see a fallback system font and we are not necessarily talking about Arial or Times here, there are way more of them than you might think there are.

If you don’t ride the progressive enhancement train and the current browser support isn’t enough for you, please consider using Bram Steins Font Face Observer.

It’s great to have a native way of controlling font loading, but it still doesn’t feel quite right, because I believe that this should also be possible with CSS. And it will be, but I’m afraid that we’ll have to wait quite a while until the font-display property is ready.

Further reading, sources and resources

1: https://dev.opera.com/articles/better-font-face/ 
2: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization#webfonts_and_the_critical_rendering_path 
3: https://googlechrome.github.io/samples/font-face-set/