Defining Custom Fonts in CSS With @font-face and font-display

@font-face is the CSS at-rule to use to define custom fonts to be made available to your web pages. With @font-face, you provide a path to a font file that’s on the same domain as your CSS file, so this mean that the rule should be used with fonts that are hosted on your servers. The rule has been around for quite some time, but a rather new property, font-display, brings a new level of loading options, which we’ll go over.

Basic Usage

Your @font-face at-rules should be defined first in your main CSS file and the most basic version of the rule should have the font-family and src properties. Here’s an example:

@font-face {
  font-family: Lato;
  src: url(/assets/fonts/Lato.woff2) format('woff2'),
       url(/assets/fonts/Lato.woff) format('woff');
}

The value for font-family will be the name used when you later use the font on your page:

h1 {
  font-family: Lato, sans-serif;
}

Notice how, for src, we provided two values. The first is for the font in the more modern woff2 format. Support for it is still lacking in some browsers, so we also provided a fallback in woff format, which has support all the way back to Internet Explorer 9.

That should cover your needs, but you can provide even more fallbacks if you wish:

@font-face {
  font-family: Lato;
  src: url(/assets/fonts/Lato.woff2) format('woff2'),
       url(/assets/fonts/Lato.woff) format('woff'),
       url(/assets/fonts/Lato.ttf) format('truetype');
}

Local version

If you’re using a font that you suspect a lot of users would already have installed on their device, you can also provide a local value to lookup first:

@font-face {
  font-family: Lato;
  src: local("Lato"),
       url(/assets/fonts/Lato.woff2) format('woff2'),
       url(/assets/fonts/Lato.woff) format('woff');
}

Note that there can be issues with loading a local version of a font. See this explanation by Paul Irish to learn more.

Style Linking

On top of defining the font name and its path, you can also define specific properties for the font. The properties that you can set are font-style, font-variant, font-weight & font-stretch. This is helpful especially to use multiple variations of a font under the same name. For example:

@font-face {
  font-family: Lato;
  src: local("Lato"),
       url(/assets/fonts/Lato.woff2) format('woff2'),
       url(/assets/fonts/Lato.woff) format('woff');
}
@font-face {
  font-family: Lato;
  src: url(/assets/fonts/Lato-LightItalic.woff2) format('woff2'),
       url(/assets/fonts/Lato-LightItalic.woff) format('woff');
 font-weight: 200;
 font-style: italic;
}

With this, if you can do something like the following example to use the Light Italic version of your font for h1 elements and the regular version for p elements:

h1, p {
  font-family: Lato, sans-serif;
}
h1 {
  font-weight: 200;
  font-style: italic;
}

Controlling Font Loading: Enter font-display

@font-face is not new, but font-display is a new property that can be used with @font-face that gives us more control with how a font is loaded.

It’s common when using custom fonts to be faced with either a FOUT (flash of unstyled text) or a FOIT (flash of invisible text) when a page is first loaded. Some browsers choose to show the text right away even if the custom font is not loaded and then revert to the custom font when its fully loaded, but this creates a FOUT. Still other browsers will have the text hidden for a short period of time to give time for the custom font to load. If the font doesn’t load during the window of time, then the fallback is used.

One way to deal with FOUT is to use a tool like Font Style Matcher to find a fallback font that’s as close to the custom font as possible, so that the font change doesn’t feel so drastic.

With font-display, we can control exactly how we want the font to be loaded:

@font-face {
  font-family: Lato;
  src: url(/assets/fonts/Lato-LightItalic.woff2) format('woff2'),
       url(/assets/fonts/Lato-LightItalic.woff) format('woff');
  font-weight: 200;
  font-style: italic;
  font-display: swap;
}

font-display can take one of 5 values:

  • auto: The default behavior, which can be different depending on the browser.
  • block: The text is first hidden for a short period, and can then change to the custom font whenever its available. This one is know as having an infinite swap period.
  • swap: The text is never hidden and changes to the custom font when its available. This also gives for an infinite swap period.
  • fallback: With fallback, the text is hidden for a very short period (the block period), then there’s a short swap period. If the custom font doesn’t make it during the swap period, then it’s not loaded at all.
  • optional: The new kid on the block, and possibly a good solution for all our font loading problems. With font-display: optional, there’s a window of only about 100ms for the custom font to load (the block period). If the font doesn’t load during that block period, the fallback font is used and the custom font is not loaded at all, but it’s still downloaded and cached behind the scenes. This means that, on subsequent page loads, the custom font should be available in the cache and will load instantly.

font-display: optional is what’s used on this website for titles. On the first page visit, users see a fallback that uses a system font, then upon visiting more pages, the custom Lato Light Italic is used instead:

@font-face {
  font-family: Lato;
  src: url(/assets/fonts/Lato-LightItalic.woff2) format('woff2'),
       url(/assets/fonts/Lato-LightItalic.woff) format('woff');
  font-display: optional;
}
h1 {
  font-family: Lato, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, 'Open Sans', FreeSans, sans-serif;
  font-style: italic;
  font-weight: 200;
}

😢 The one downside right now is that support for font-display is still very limited.

Browser Support

Can I Use css-font-rendering-controls? Data on support for the css-font-rendering-controls feature across the major browsers from caniuse.com.

✖ Clear

🕵 Search Results

🔎 Searching...