CSS font-display: The Future of Font Rendering on the Web

Giulio Mainardi
Share

One of the downsides of using web fonts is that if a font is not available on a user’s device, it must be downloaded. This means that before the font becomes available the browser has to decide how to handle the display of any block of text that uses that font. And it needs to do so in a way that doesn’t significantly impact the user experience and perceived performance.

In the course of time, browsers have adopted several strategies to mitigate this problem. But they do this in different ways and out of the control of developers, who have had to devise several techniques and workarounds to overcome these issues.

Using Chrome DevTools to test font -display

Enter the font-display descriptor for the @font-face at-rule. This CSS feature introduces a way to standardize these behaviors and provide more control to developers.

Using font-display

Before looking in detail at the various features offered by font-display, let’s briefly consider how you might use the feature in your CSS.

First of all, font-display is not a CSS property but, as mentioned in the intro, it is a descriptor for the @font-face at-rule. This means that it must be used inside a @font-face rule, as showed in the following code:

@font-face {
  font-family: 'Saira Condensed';
  src: url(fonts/sairacondensed.woff2) format('woff2');
  font-display: swap;
}

In this snippet I’m defining a swap value for the font Saira Condensed.

The keywords for all the available values are:

  • auto
  • block
  • swap
  • fallback
  • optional

The initial value for font-display is auto.

In later sections I’ll go over each of these values in detail. But first, let’s look at the time period that the browser uses to determine the font to be rendered. When discussing each of the values, I’ll explain the different aspects of the timeline and how these behave for each value.

The font-display Timeline

At the core of this feature is the concept of the font-display timeline. The font loading time, starting from its request and ending with its successful loading or failure, can be divided into three consecutive periods that dictate how a browser should render the text. These three periods are as follows:

  • The block period. During this period, the browser renders the text with an invisible fallback font. If the requested font successfully loads, the text is re-rendered with that requested font. The invisible fallback font acts as a blank placeholder for the text. This reduces layout shifting when the re-rendering is performed.
  • The swap period. If the desired font is not yet available, the fallback font is used, but this time the text is visible. Again, if the loading font comes in, it is used.
  • The failure period. If the font does not become available, the browser doesn’t wait for it, and the text will be displayed with the fallback font on the duration of the current page visit. Note that this doesn’t necessarily mean that the font loading is aborted; instead, the browser can decide to continue it, so that the font will be ready for use on successive page visits by the same user.

Adjusting the duration of such periods allows you to configure a custom text rendering strategy. In particular, these durations can collapse to zero or extend to infinity, as I’ll show you in the following sections.

But these durations cannot be explicitly assigned by the developer. This possibility was examined in an early stage of the specification, but was dropped. Instead, a set of predefined keyword values that can handle the majority of use cases are provided, as outlined in the previous section.

Let’s look at how each of these keywords manages the font loading and display process.

font-display: auto

This value tells the browser to adopt the default font display behavior chosen by the browser. Often this strategy is similar to the next value, block.

font-display: block

With this value, after a short block period (the specification recommends a duration of three seconds), the swap period extends to infinity. This means that in this circumstance the failure period is absent.

While the browser briefly waits for the requested font, it renders the text with the invisible fallback font; after that, if the font is not yet available, the fallback font is made visible; and whenever the download completes, the browser re-renders the text with the wanted font.

You can watch this behavior in the following video, which uses a simple test page that incorporates a specific web font for its heading:

At the beginning of page load, the heading is invisible but it is there, in the DOM. After about three seconds, if the font is not yet available, the text is made visible with the fallback font. In the video demo, I’m mimicking poor network conditions using Chrome DevTools’ network throttling feature. Finally, when the font manages to download, the heading is re-rendered with it.

font-display: swap

With this value, the block period collapses to 0 and the swap period extends to infinity. Therefore, here too, the failure period is missing.

In other words, the browser doesn’t wait for the font but instead immediately renders the text with the fallback font; then, whenever the font is available, the text is re-rendered with it.

Let’s verify this:

font-display: fallback

This is the first value that incorporates the failure period. After a very short block period (100 ms is recommended), the swap period now has a short duration (3s is recommended). As a result, if the requested font is not ready at the end of this period, the text will display using the fallback font for the duration of the page visit. This avoids disturbing the page visitor with a late layout shift that could be jarring to the user experience.

In this first video below, the font loads after more than six seconds, thus it is never swapped in:

In the next video, the font loads faster, before the timeout of the swap period kicks in, so the font is used as expected:

font-display: optional

When I first read the specification, I found the names assigned to the font display strategies not so clear. This is even pointed out in the specification itself, which suggests future versions of the spec use names that better illustrate the intended use of each strategy, proposing the following alternatives:

  • requires for block
  • important for swap
  • preferable for fallback

But the optional value is expected to remain unchanged. Indeed this value nicely captures the essence of the behavior it triggers. In this case, the font is considered optional for the rendering of the page, essentially telling the browser: if the font is already available, use it, otherwise it doesn’t matter, go ahead with the fallback font; the font can be ready for use on future page visits.

With this value, the font display timeline has a short block period (again, the spec recommends a 100 ms time interval) and a zero-duration swap period. Hence the failure period immediately follows the block period, meaning that if the font is not readily available, it will not be used for the duration of the page visit. But the font could eventually be fully downloaded in the background and so it would become available for immediate rendering on future page loads.

But I should point out here that, especially under poor network conditions, the user agent is free to abort or even to not begin the font download. This is so as to not unnecessarily impact the quality of the network connection. Therefore the site is still usable but the font won’t be immediately available on future page loads.

In the video below, the test page is loaded without throttling the network. The font is downloaded quickly, but only after the short block period, so the text is displayed with the fallback font for all duration of the visit.

In the next video, the page is reloaded under the same network conditions, but this time with the cache enabled, to simulate a second visit:

And there you have it, the heading now renders with the desired web font.

Before moving on, note the extremely short duration of around 100 ms recommended for the block period when using the fallback and optional values. This is to allow a brief period for a quick-loading font (or one loading from the cache) to display before using the fallback font, thus avoiding a “flash of unstyled text”, or FOUT.

I actually wondered why the block period collapses to zero when using font-display: swap, instead of using the same short interval as optional. It turns out, there is an open issue in the spec’s GitHub repo to make ‘swap’ use the same “tiny block period” as others.

About the Fallback Font

In the above discussion, several times I mentioned the fallback font. But where does this come from?

The fallback font is the first available font present in the font stack defined using the font-family property on the element in question.

For example, on the test page, the font-family value for the heading is:

h1 {
  font-family: 'Saira Condensed', Arial, "Helvetica Neue", Helvetica, sans-serif;
}

This can be verified (see the video above for optional), for example, on a Windows machine, which uses Arial as the rendered font.

Support

At the time of writing support for the font-display descriptor looks as follows:

  • Chrome has supported it since version 60
  • Opera has supported it since version 47
  • It’s in development for Firefox and has been available behind a flag since version 46.
  • Regarding Safari, the WebKit platform status reports that it is in development
  • There is no indication yet that Microsoft Edge will support it anytime soon. There is a ticket on the Microsoft Edge Developer Feedback site where it is possible to vote for the implementation of this feature.

Please refer to caniuse.com for up-to-date support information.

It is worth noting that font-display support cannot be tested by feature queries, because, as mentioned above, it is not a CSS property but a font descriptor. In this GitHub issue you’ll find some discussion on how to properly detect this feature.

Once it has been detected that font-display is not supported, several fallback strategies are possible, but this is out the scope of this article. The article A Comprehensive Guide to Font Loading Strategies by Zach Leatherman presents an exhaustive survey of available solutions.

Usage with Google Fonts

You may have noticed that the font used in the demo page is from Google Fonts, but it is not loaded in the usual way, i.e., linking to the stylesheet provided by the font provider. Instead, I just copied the URL of the font found in that stylesheet and used that URL in my custom @font-face rule. I had to do this because, as seen in the usage section, font-display must be specified inside the font-face rule.

Is there a better and more Google Fonts-friendly way? Are Google Fonts and other third-party font foundries going to support font-display?

There is an open issue on the Google Fonts GitHub repo where this is discussed. Add your +1 to show your interest in this feature!

Also, it’s worth mentioning that the CSS Fonts Module Level 4 proposes the usage of font-display as a descriptor for @font-feature-values, to enable developers to set a display policy for @font-face rules that are not directly under their control. But this is not yet implemented by any user agent.

Final Words

I hope this gives you a decent overview of the font-display descriptor and how this feature foreshadows a strong future for font rendering on the web.

Although this article didn’t discuss specific use cases for the different strategies implemented by font-display, the specification illustrates use cases with some clear examples, and several of the cited references elaborate on this topic. So in addition to the basics I’ve covered here, you’ll have more to look over in the resources I’ve referenced.