CSS font-display: The Future of Font Rendering on the Web
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.
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
forblock
important
forswap
preferable
forfallback
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.