Tutorial

How To Define Custom Fonts in CSS with @font-face and font-display

CSS

Introduction

@font-face is a CSS at-rule used to define custom fonts. With @font-face, you provide a path to a font file hosted on the same server as your CSS file. The rule has been around for quite some time, but there is a newer property, font-display, which brings a new level of loading options.

In this tutorial, we will download the popular, open-source font, Roboto Mono, and use @font-face to load the font on a sample webpage. To create the best user experience, we will then use the font-display property to customize how and when to load it.

Prerequisites

Step 1 — Downloading the Fonts and Building a Web Page

Before we begin exploring the @font-face rule, let’s set up a sample web page and directory.

From a working directory, make a new folder for our website and a subdirectory for our font files:

  • mkdir -p ./website/fonts/

Navigate into our new project’s root directory, website:

  • cd website

We will run all remaining commands from here.

Now we will use the curl command to download the Roboto Mono font. We are using a popular app called google-webfonts-helper, which allows us to download multiple fonts directly from Google’s Content Delivery Network in a single, neatly bundled GET request.

Let’s download two different styles and weights of Roboto Mono, regular and 700italic:

  • curl -o ./fonts/fontfiles.zip "https://google-webfonts-helper.herokuapp.com/api/fonts/roboto-mono?download=zip&subsets=latin,latin-ext&variants=regular,700italic&formats=woff,woff2,ttf"

Note how we are specifying the variants that we want from the Roboto Mono font family. We then specify the formats that we would like for each. We are requesting the ttf, woff, and woff2 formats. The woff2 format is the most modern web font format, but support for woff2 is still lacking in some browsers. Therefore, we are also providing a fallback in woff format, which has support back to Internet Explorer 9, and a TrueType format, or ttf. This will give us excellent coverage, but when we write our CSS, we will provide more fallback options using standard fonts. The font-display property will also help us manage how we load fonts for various users.

Now unzip the downloaded contents to our ./fonts folder. On machines running Linux and macOS, use the following command:

  • unzip ./fonts/fontfiles.zip -d ./fonts

Examine the contents of the ./fonts folder:

  • ls ./fonts

We now find six new files—a .ttf, a .woff, and a .woff2 file per font.

With our fonts downloaded, let’s write some CSS and use it to style an HTML element.

Step 2 — Using the @font-face Rule

In this step, we will apply our downloaded fonts using the @font-face property.

Using nano or your preferred text editor, create and open a file called style.css:

  • nano style.css

Add the following content, which will define the @font-face rule with paths to our files:

style.css
@font-face {
  font-family: 'Roboto Mono', monospace;
  src: url(fonts/roboto-mono-v12-latin-regular.woff2) format('woff2'),
       url(fonts/roboto-mono-v12-latin-regular.woff) format('woff'),
       url(fonts/roboto-mono-v12-latin-regular.ttf) format('truetype');
}

@font-face {
  font-family: 'Roboto Mono', monospace;
  src: url(fonts/roboto-mono-v12-latin-700italic.woff2) format('woff2'),
       url(fonts/roboto-mono-v12-latin-700italic.woff) format('woff'),
       url(fonts/roboto-mono-v12-latin-700italic.ttf) format('truetype');
  font-weight: 700;
  font-style: italic;
}

h1, p {
  font-family: 'Roboto Mono', monospace;
}

h1 {
  font-family: 'Roboto Mono', monospace;
  font-weight: 700;
  font-style: italic;
}

Let’s examine this code block by block.

Always define your @font-face at-rules first in your main CSS file. The most basic version of the @font-face rule requires the font-family and src properties. In our first block, we provide Roboto Mono as the value for font-family, and we provide paths to our three files for src, each with a different format and in descending order of priority.

In our second block, we provide the same font-family value, but we use paths to our 700italic version of Roboto Mono. We then define two properties, font-weight and font-style. We will use these properties to define where we want to use this second version of Roboto Mono.

In our next two blocks, we are defining custom styles for <h1> and <p> elements. Note how we use the font-family to define both but then add font-weight and font-style to a block specific to <h1>. This will render our H1 headings in Roboto Mono 700italic instead of Roboto Mono regular.

Save and close the file.

Now let’s build a small HTML page with some <h1> and <p> tags.

Create and open a new file called index.html:

  • nano index.html

Add the following code, which will define a heading and a line of text:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>CSS @font-face</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1> Roboto Mono font, 700italic </h1>
  <p> Roboto Mono font, regular </p>
</body>
</html>

Save and close the file.

Load index.html in a web browser. You will see that your heading is rendered in Roboto Mono 700italic while your paragraph text is rendered in Roboto Mono regular, like this:

Roboto Mono font, 700 italic

Roboto Mono font, regular

With our @font-face property working, let’s now use font-display to configure how and when the fonts are loaded.

Step 3 — Using font-display to Control Font Loading

With font-display, we can control exactly how we want to load our fonts. This can greatly improve the user experience when using custom fonts.

Sometimes, when using custom fonts, a user can encounter 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 our text right away, even if the custom font is not loaded. The browser will revert to the custom font once it fully loads, but this creates a FOUT. Other browsers will hide the text for a short period until the custom font load, causing a FOIT. If the font doesn’t load during the window of time, the browser will use a fallback font.

One way to deal with FOUTs is to use a tool like Font Style Matcher to find a fallback font that is as close to the custom font as possible so that the font change doesn’t feel so drastic. We can, however, use the font-display property to handle these issues more elegantly.

To confront loading issues, font-display takes one of 5 values:

  • auto: This uses the browser’s default behavior, which will vary.
  • block: The text is first hidden for a short period, but will change to the custom font when it becomes available. This one value is said to have an infinite swap period.
  • swap: The text is never hidden and changes to the custom font when it becomes available. This also provides for an infinite swap period.
  • 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 load within the swap period, then it isn’t loaded at all.
  • optional: The text is given a very brief block period to load (~100ms). If the font doesn’t load during that block period, the fallback font is used and the custom font is not loaded at all. However, the font is still downloaded and cached behind the scenes. This means that, on subsequent page loads, the custom font will become available in the cache and then will load instantly.

The optional value for font-display provides a robust solution to many font loading situations. Let’s add it to our own CSS.

Reopen style.css:

  • nano style.css

Now add the highlighted code:

style.css
@font-face {
  font-family: 'Roboto Mono', monospace;
  src: url(fonts/roboto-mono-v12-latin-regular.woff2) format('woff2'),
       url(fonts/roboto-mono-v12-latin-regular.woff) format('woff'),
       url(fonts/roboto-mono-v12-latin-regular.ttf) format('truetype');
  font-display: optional;
}

@font-face {
  font-family: 'Roboto Mono', monospace;
  src: url(fonts/roboto-mono-v12-latin-700italic.woff2) format('woff2'),
       url(fonts/roboto-mono-v12-latin-700italic.woff) format('woff'),
       url(fonts/roboto-mono-v12-latin-700italic.ttf) format('truetype');
  font-weight: 700;
  font-style: italic;
  font-display: optional;
}

h1, p {
  font-family: 'Roboto Mono', monospace;
}

h1 {
  font-family: 'Roboto Mono', monospace;
  font-weight: 700;
  font-style: italic;
}

Now our custom font will either load so quickly that the user will never experience a FOUT or a FOIT, or it won’t load at all. However, it will still download and load instantly upon refreshing or visiting more pages.

Conclusion

In this tutorial, we downloaded a custom font and used the @font-face property to add it to a webpage. We then used the font-display property to manage how the custom font will load, if at all. To learn more about font-display and related properties, consider exploring the related documentation on the Mozilla Developer Network.

Creative Commons License