`font-display` for the Masses
The following is a guest post by Jeremy Wagner, a web developer, author, and speaker living in the Twin Cities. He’s got a new book on web performance. Here we’re going to dig into a brand new CSS feature, that once it percolates through the browser ecosystem, will be great for performance.
If you’re a regular reader here at CSS-Tricks, chances are good that you’re familiar with using web fonts. You may even know a few useful tricks to control how fonts load, but have you used the CSS font-display
property?
The font-display
property in CSS is newly available in Blink-based browser. It gives us the same power found in browser features such as the Font Loading API and third party scripts such as Bram Stein’s Font Face Observer. The key difference, though, is that this capability is also now a CSS feature.
If this is new to you, no sweat. Let’s first talk a bit about default font loading behavior in browsers.
Font Loading in the Browser
Browsers are carefully crafted programs that do a lot under the hood that we may not suspect. Font loading falls under that category. When a browser loads a font, text that is styled to use that typeface is hidden until the font is loaded. This is known as the “Flash of Invisible Text,” or FOIT.
This loading behavior is there to save us from seeing unstyled text, but it can have repercussions for users on slow connections. By default, most browsers will hide text for up to three seconds until displaying fallback text while waiting for a font to load. Other browsers such as Safari will wait even longer, or even indefinitely, leaving some users with text that never renders.
The way we mitigate this problem currently is by using a JavaScript-based solution (like Font Face Observer) to track when fonts are loaded. We style the document using system fonts first. When we can detect that custom fonts are loaded via JavaScript, we add a CSS class to the document that then applies our custom fonts. This approach has been covered before. For example, assume you have a page that uses Open Sans Regular for all
tags. By default, you specify this rule:
p {
font-family: "Arial", "Helvetica", sans-serif;
}
As the font loads, either Arial or Helvetica (depending on the fonts available on your system) will display first. When we’ve verified that Open Sans Regular has loaded using JavaScript, we then apply a class of fonts-loaded
to the tag, which will apply Open Sans to the
tag via this CSS:
.fonts-loaded p {
font-family: "Open Sans Regular";
}
These solutions work, but they can be rather unwieldy. That’s where the new font-display
CSS property comes in.
Getting Acquainted with font-display
font-display
is a new CSS property that is available as an experimental feature that first shipped in Chrome 49 and is now also in Opera and Opera for Android. Using it, we can control how fonts display in much the same way that we can with JavaScript-based solutions, only now through a convenient CSS one-liner!
font-display
is used inside of a @font-face
declaration for your custom fonts. This property accepts a number of values:
auto
: The default value. The typical browser font loading behavior will take place, which is to hide typefaces that use custom fonts, and then display the affected text when fonts finish loading.swap
: The fallback text is shown immediately until the custom font loads. In most cases, this is what we’re after. JavaScript-driven font loading solutions almost always aim to emulate this setting.fallback
: This is sort of a compromise betweenauto
andswap
. There will be a short period of time (100ms according to Google) that text styled with custom fonts will be invisible. The unstyled text will then appear (if the custom font hasn’t already loaded by this point.) Once the font loads, the text is styled appropriately.optional
: Operates exactly likefallback
in that the affected text will initially be invisible for a short period of time, and then transition to a fallback font. The similarities end there, though. Theoptional
setting gives the browser freedom to decide whether or not a font should even be used, and this behavior hinges on the user’s connection speed. On slow connections, you can expect that custom fonts may not load at all if this setting is used.
Now that we know what values font-display
accepts, we can apply it to a @font-face
rule. Here’s an example of using the swap
value in a @font-face
declaration for Open Sans Regular:
@font-face {
font-family: "Open Sans Regular";
font-weight: 400;
font-style: normal;
src: url("fonts/OpenSans-Regular-BasicLatin.woff2") format("woff2");
font-display: swap;
}
Of course, we’re abbreviating the font face set in this example by only using a WOFF2 file, but I’m going for brevity, here. In this instance, we’re using the swap
option for font-display
, which yields a loading behavior like you see in the image below:
When we control font loading in JavaScript, this is the behavior we’re after. We want to make sure that the text is visible by default, then apply the custom font after it has been downloaded.
What constitutes “fallback text”, though? When you specify a font-family
for an element, you do so with a comma-separated list of fonts. The fallback is whatever system font that’s next in line after the custom font:
p {
font-family: "Open Sans Regular", "Helvetica", "Arial", sans-serif;
}
In this example font stack, the custom font is Open Sans Regular, and the system fonts are Helvetica and Arial. When font-display: swap;
is used, the initial font that’s displayed is the first system font in the font stack. When the custom font has loaded, it will kick in and replace the system font that was initially displayed. Using the other font-display
values of fallback
and optional
rely on the system font fallbacks in the stack before deciding what to do with the custom font.
Most of the time you’ll want to use swap
.
If you don’t know which option to use, then go with swap
. Not only does it provide an optimal balance between custom fonts and accessibility of content, it provides the same font loading behavior that we’ve relied on JavaScript for. If you have fonts on the page that you’d like to have load, but could ultimately do without, consider going with fallback
or optional
when using font-display
.
What if font-display
isn’t supported?
The only failing of font-display
is that support for it isn’t very wide yet. Therefore, you have two choices concerning its use:
- You can just use
font-display
and that’s that. If other browsers don’t support it, they’re not going to get the benefits it provides. This isn’t necessarily bad in that it doesn’t break anything, but it could leave users of non-Blink browsers out in the cold. - You can detect support for
font-display
and provide an alternative. If time and resources allow, this is what you should do.
If decide to go with option 2, you’ll need a way to detect support for font-display
. Fortunately for you, doing so is a simple task:
if ("fontDisplay" in document.body.style === false) {
/* JavaScript font loading logic goes here. */
}
It’s the perfect fallback. From here, we can decide what we want to do, whether it means falling back to a third party script like Font Face Observer, or to the native font loading API available in Firefox, Chrome and Opera:
if ("fontDisplay" in document.body.style === false) {
if ("fonts" in document) {
document.fonts.load("1em Open Sans Regular");
document.fonts.ready.then(function(fontFaceSet) {
document.documentElement.className += " fonts-loaded";
}
}
}
Note from editor: See comments here about feature detection. Might need to do this differently to get more accurate cross browser compatibility results.
Here we go the usual route, and let the font loading API handle the transition for us. Once the API knows that a font has loaded, we can apply a class of fonts-loaded
to the tag. With this class applied, we can then write CSS that allows a progressive application of the custom typeface:
p {
font-family: "Helvetica", "Arial", sans-serif;
}
.fonts-loaded p {
font-family: "Open Sans Regular";
}
Obviously, we’d prefer to use a CSS one-liner like font-display
to do all of this stuff for us, but at least we have the ability to fall back to another solution if need be. As time marches on, we can expect that this solution, like the font loading API, will find purchase in other browsers.
What About Third Party Font Providers?
If you embed fonts using a third party service like Google Fonts or TypeKit, there’s not much you can do. The font-display
property must be used inside of a @font-face
declaration. Because you don’t control the CSS that the font provider serves, you can’t control the presence of the font-display
property in that instance, much less the value that’s passed to it.
It is likely, though, that as time goes on, these providers will modify the CSS they serve to include a font-display
property, or allow it as a configurable option when you retrieve an embed code on their service.
Either way, font-display
is a welcome addition to the web typography landscape. It greatly simplifies what is otherwise an unwieldy sort of task in JavaScript. If you’re using Chrome, go to `chrome://flags/#enable-experimental-web-platform-features` and enable Experimental Web Platform Features and try out font-display
for yourself!
Jeremy Wagner is the author of Web Performance in Action, an upcoming title from Manning Publications. Check him out on Twitter: @malchata
`font-display` for the Masses is a post from CSS-Tricks