In the early days of CSS, font choice was limited to whatever fonts users had installed on their system, and generic font values such as sans-serif and monospace . Towards the end of the “aughts”, however, CSS introduced web fonts and the @font-face CSS rule. Web design and typography changed forever.
With @font-face , we can use just about any font for our web pages, as long as the font is available in a browser-compatible format.
1. Setting an @font-face Rule
Here’s what a basic @font-face rule set looks like. This is the bare minimum you’ll need in order to use a web font:
The @font-face at-keyword tells the browser that we want to use an external font file. The font-family line sets a descriptor, or nickname, for this font. Don’t confuse this with the font-family property. When used within an @font-face rule set, font-family sets the value that will be used for CSS font-name matching. The last line defines a font source with the src descriptor, which is the location of a font file.
To apply this font to your text, include the descriptor value in the font or font-family declaration:
font: 16px / 1.5 ‘MyAwesomeFont’, sans-serif;
The browser will match instances of MyAwesomeFont to the source we’ve specified in our @font-face rule set. If MyAwesomeFont isn’t available, or the browser doesn’t support web fonts, it will fall back to the sans-serif generic.
Just because we’ve defined a font for use doesn’t mean the browser will load it. Our font also needs to be in a format the browser can parse. For current browsers, that means WOFF2. However, a surprising number of web users don’t or can’t update their devices. We can accommodate these users by defining multiple font sources.
2. Using Multiple Font Formats
While the @font-face example above takes care of the latest and greatest browsers, older browser versions lack support for the WOFF2 format. They do, however, support its predecessor, WOFF. Let’s update our @font-face rule to provide a WOFF alternative:
src: url(‘http://example.com/fonts/myawesomefont.woff2’) format(‘woff2’),
The src descriptor takes the format <url> format() , where <url> is the location of a font resource, and format() is a format hint. We can provide multiple src options by separating them with a comma. Using format() helps the browser select a suitable format from the ones provided. Its argument should be one of woff , woff2 , truetype , opentype , or embedded-opentype . In this example, browsers that don’t support WOFF2 will download the WOFF-formatted font file instead.
You may see examples of @font-face rules that include EOT, SVG, TrueType, or OpenType font formats. You can safely exclude these formats. EOT font support is limited to ancient versions of Internet Explorer 9 and below. Most browsers have removed support for SVG fonts, or never implemented it to begin with. TrueType and OpenType enjoy wide browser support, but WOFF2 file sizes are much smaller. The only reason to use either format is if the font in question isn’t available as a WOFF2-formatted or WOFF-formatted file.
3. Fonts and Origins
Web fonts are subject to the same-origin policy. Under this policy, a browser loads a resource only if it shares the same “origin” as the requesting document. An origin is the combination of a document’s scheme or protocol, host name, and port number.
In other words, if your web page is served from https://example.com and your fonts are served from https://cdn.example.com , they won’t load. To get around this restriction, you’ll need to enable “cross-origin resource sharing”.
Cross-origin resource sharing, or CORS, is a system of headers that tell the browser whether or not a document from a requesting origin has permission to use a requested asset from another. A full discussion of CORS is well beyond the scope of this book, but I’ll try my best to explain it.
When an HTML or CSS document links to external assets, the browser first checks whether those assets share the same origin as the requesting script or file. If so, it loads the asset.
If the requesting document doesn’t share the same origin as the requested resource, the browser makes a “preflight request” for the resource. A preflight request asks the external server: “Does https://example.com have permission to load GreatGroteskWebFont.woff2?” If the server response includes the Access-controL-ALLow-origin response header and https://example.com as its value, the browser follows up with a get request for the font file and loads it. If the response doesn’t include that, the browser won’t make the get request and the font won’t be loaded.
To enable CORS, you’ll need to add an Access-controL-ALLow-origin response header to your font URLs. This header grants permission to the requesting document’s origin. Origins must not have a trailing slash. Here’s an example:
Adding headers requires access to your server or content delivery network configuration. If you don’t have such access, or don’t feel comfortable managing headers, you have two options:
- serve your font files from the same origin as your document
- use a hosted web font service such as Google Fonts (free), Adobe Fonts or Fontspring
Hosted services implement their own cross-origin headers so that you don’t have to worry about it.
4. Using Multiple Font Weights and Styles
A font is actually a collection of typefaces or faces. A face is a single weight, width, and style of a font. EB Garamond is a font. EB Garamond Regular and EB Garamond Bold Italic are faces. Most people use the terms interchangeably, but differentiating between the two is helpful here.
When incorporating a web font into your site’s design, you may also want to incorporate its stylistic variants for bolded or italicized text. We can do this using the font-weight and font- style descriptors. These descriptors tell the browser which face (and corresponding file) to match with a particular weight or style:
font-family: ‘EB Garamond Regular’;
src: url(‘EB-Garamond-Regular.woff2’) format(‘woff2’),
The next line is optional, since this is the initial value. It’s the equivalent of font-weight: 400
font-family: ‘EB Garamond Italic’;
src: url(‘EB-Garamond-Italic.woff2’) format(‘woff2’), url(‘EB-Garamond-Italic.woff’) format(‘woff’); font-style: italic;
font-family: ‘EB Garamond Bold’;
src: url(‘EB-Garamond-Bold.woff2’) format(‘woff2’), url(‘EB-Garamond-Bold.woff’) format(‘woff’);
font-weight: bold; /* The equivalent of font-weight: 700 */
font-family: ‘EB Garamond Bold Italic’;
src: url(‘EB-Garamond-Bold-Italic.woff2’) format(‘woff2’), url(‘EB-Garamond-Bold-Italic.woff’) format(‘woff’); font-weight: bold;
In the example above, we’ve matched faces from the EB Garamond font family with an appropriate style and weight. Here, too, font-weight and font-style are descriptors that tell the browser to download an additional font file to display weight and style variants, should the page use bold and/or italic text.
Browsers synthesize bold or italic text from the primary font when an appropriate weight or style isn’t available. However, this may lead to less readable or less attractive text. Compare the synthetic italic text (using EB Garamond, top) to the italic version (EB Garamond Italic) of this font in the image below.
That said, pretty isn’t always fast. Using multiple faces increases the amount of data that must be sent to the browser. As with most aspects of web development, you’ll need to make trade-offs between style and performance.
Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.