This post is pretty old, and might contain outdated advice or links. We’re keeping it online, but recommend that you check newer posts to see if there’s a better approach.
All blog postsDon't let Flash of Unstyled Text become a game of Whack‑a‑Mole
The ability to use any font on our websites, irrespective of its availability on a visitor's computer, has given us great creative freedom. It is no wonder that almost two-thirds of the Alexa Top 1 Million sites use custom fonts. Unfortunately, the use of web fonts is not without its drawbacks. Default browser behaviour actually decreases user experience by initially hiding our content, while workarounds often trigger sudden text reflows. By slightly altering the metrics of your initial font we can reduce the chance of reflows happening.
Your content is there, yet the user sees nothing
Web fonts are assets that need to be loaded from the server before they can be used. Obviously this takes time, but more problematically it can cause the dreaded Flash of Invisible Text or FOIT for short. This happens when the styles are loaded and applied to the page before the font is loaded. The browser will wait until the font is loaded before rendering the text set in that font.
Solution to this problem is bypassing the browser's default behaviour, and only use web fonts when they are actually loaded. As explained in another blog post, we use Bram Stein's Font Face Observer on this site to escape FOIT. Most newer browsers have a native API for this, thanks to the CSS Font Loading Module.
A sudden change of font
By replacing the font after it is loaded, the visitor is presented with a readable page as soon as possible, meaning that visitors can already read—and interact with—the content instead of staring at a blank page. However, we now have to deal with FOUT, a Flash of Unstyled Text. When the custom font is loaded, the content is re-rendered in that font. When font metrics between the initial font and the custom font differ, the content reflows. Just like with loading responsive images, this can have undesired consequences, such as shifting hit areas.
Consider this example:
As soon as our web font is available and applied, the link in the example above reflows. In many cases the hit areas do not even overlap. Now imagine this happening to a table of contents for instance, and you see how these reflows can ruin user experience. If only we could reduce flow.
Approximating the metrics of our web font
Since the problem occurs after styling is loaded and before the fonts are loaded, we can use CSS to adjust our initial font metrics to fit the space of enhancement font. When done correctly, a user might see the fonts change, but reduce the change of a reflow.
- First off, we should make sure fonts share the same line height by explicitly setting it. Failing to do so implies
line-height: normal
, telling the browser to pick a value at its discretion based on the font. Different fonts get different line heights, typically ranging from 1.0 to 1.2. - After that, we want to approximate the horizontal metrics so that the characters in the different fonts align. We can do this by slightly increasing tracking - the space between characters - by adjusting the
letter-spacing
CSS property.
Combining this would give us a stylesheet that would look a little like this:
@font-face {
/* Define custom font */
font-family: 'Noto';
src: url('/path/to/noto.woff2')
format('woff2');
}
body {
/* Define initial web-safe font */
font-family: Georgia, sans-serif;
/* Declare a line-height */
line-height: 1.6;
/* Approximate tracking */
letter-spacing: 0.03825em;
}
.fonts-loaded body {
/* Define custom font */
font-family: 'Noto';
/* Reset tracking */
letter-spacing: 0;
}
This process is by no means an exact science. Font variants and weights, even the content of the language can influence what value for letter-spacing
works for you. We had to set negative values for <strong>
tags, offsetting the value on body
, for instance. It is however a pragmatic way to minimise the chance of a reflow, making sure users never miss a click.