Astro Font Fallbacks with Capsize: reduce CLS
Tags: astro, javascript, webdev, css
Published: 2023-06-05 08:39:04

---
title: "Astro Font Fallbacks with Capsize: reduce CLS"
published: "true"
description: "Astro Font Fallbacks ☄️ how you can use Capsize to reduce layout shift from font swapping when optimizing your site for 🧑🏽 user experience."
tags: "astro, javascript, webdev, css"
canonical_url: "https://rodneylab.com/astro-font-fallbacks/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkhhc6lw90qus72w93u3.png"

---
## 🚀 Font Fallbacks

In this Astro font fallbacks post, we see how you can use Capsize to reduce layout shift caused by font swaps.  We focus on self-hosted fonts in vanilla CSS here, though you can apply the techniques elsewhere.  Before tucking into the detail, we will unpack some terms just mentioned.

### Font Swap

When you use web fonts or Google fonts on a site, it can take a moment for them to be downloaded to the user device, especially if they have a slow internet connection.  By default, the user will see nothing, where the text should be, while the font gets downloaded in the background.  To improve user experience, you will typically want to add the `font-display: swap` directive to your `@fontface` CSS.  This tells the browser to display the text using a fallback font initially.  That improves user experience as the visitor can start reading while the web font downloads.  The browser swaps out the fallback for the web font, once it has downloaded it, so the page will ultimately look as the designer wanted it to.

### Font Swap Layout Shift

We solve one user experience issue, but potentially introduce another.  That is because not all fonts are the same size!  When the browser switches from fallback to web font, there is typically some layout shift, just resulting from the two fonts having different metrics.  This becomes more pronounced on narrower displays.  Cumulative Layout Shift (**CLS**) is not great; it can be annoying if a button that you want to tap shifts just before your finger hits the screen.

One trick to reduce this font swap layout shift is to use modern CSS to adjust the metrics of the fallback font.  Doing this, the fallback font will not look exactly as it should, but the shift can be dramatically reduced, or even eliminated on font swap.  We look at techniques for doing that here, using Capsize.

![Astro Font Fallbacks: Two browser windows side-by-side, one shows the fallback Times New Roman font while the other shows a sans font.  Despite the text being the same, layout is shifted.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qs36qtoed73mpnalho9a.png)

## 😕 What is Capsize?

<a href="https://seek-oss.github.io/capsize/">Capsize is a typography tool for text layouts</a>.  Creating precise text layouts can be frustrating, just from the way font sizing is defined.  Capsize helps here, <a href="https://rodneylab.com/svelte-capsize-styling/">making your text height and the spacing above and below more predictable</a>.  The font metric data it uses here is also handy for working out the modern CSS adjustments we can make to a fallback font, so its layout approximates the web font&rsquo;s.

An alternative to Capsize for reducing <a href="https://github.com/danielroe/fontaine">font swap layout shift is Fontaine</a>.  It has a Vite plugin, and is able automatically to update font face CSS (like we will do, using Capsize metrics).  We do something a little more manual here with Capsize, just to help get a grounding in the issue and solution approach.

## 🧱 What are we Building?

We won&rsquo;t build a site from start-to-finish.  Instead, we will jump into snippets for a basic example with vanilla CSS and self-hosted fonts.  There is a link to the complete code further down the page.  You will be able to use that code to play around a little with font swaps and see the impact of our approach.


If you are new to working with self-hosted fonts in Astro, see the <a href="https://rodneylab.com/astro-self-hosted-fonts/">Astro Self-Hosted Fonts workflow video</a> to get up to speed.  If you want more background on font swapping and fallbacks, before continuing, see the <a href="https://developer.chrome.com/blog/font-fallbacks/">thorough article by Katie Hempenius on Chrome Developers</a>.

## 🌍 Astro Font Fallbacks: Global Styles

Our first code snippet is the global style sheet. The most important feature here is setting and using a custom CSS property for the font family:

```css
:root {
	/* ...TRUNCATED */

	--font-family: 'Open Sans', sans-serif;
}
```

We will override that property in the font fallback CSS which we generate.

## 💄 font-face CSS

These are the font-face directives needed for our Open Sans font.  Later, these CSS files will be imported into the Astro markup file.

```css
/* open-sans-regular - latin */
@font-face {
	font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
	font-family: 'Open Sans';
	font-style: normal;
	font-weight: 400;
	src: url('/fonts/open-sans-v35-latin-regular.woff2') format('woff2'),
		/* Chrome 36+, Opera 23+, Firefox 39+ */ url('/fonts/open-sans-v35-latin-regular.woff')
			format('woff'); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
 
/* open-sans-700 - latin */
@font-face {
	font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
	font-family: 'Open Sans';
	font-style: normal;
	font-weight: 700;
	src: url('/fonts/open-sans-v35-latin-700.woff2') format('woff2'),
		/* Chrome 36+, Opera 23+, Firefox 39+ */ url('/fonts/open-sans-v35-latin-700.woff')
			format('woff'); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
```

You can download woff files (and matching font-face directives) for most Google fonts with the <a href="https://gwfh.mranftl.com/fonts/open-sans?subsets=latin">google-webfonts-helper app</a>.  Note, font-face includes `font-display: swap`, which is important here.

## 📜 Capsize Script

Next, I added a TypeScript stand-alone script: `capsize-font-fallbacks.ts` in the project root directory.  This uses node APIs to output an extra CSS stylesheet; convenient when working in vanilla CSS.  You could, alternatively, use similar code as a utility function to export styles for use on a DOM element: more useful when working with a CSS-in-JS framework or something like <a href="https://rodneylab.com/astro-vanilla-extract/">vanilla-extract</a>.

```typescript
import { createFontStack } from '@capsizecss/core';
import arial from '@capsizecss/metrics/arial';
import openSans from '@capsizecss/metrics/openSans';
import roboto from '@capsizecss/metrics/roboto';
import { writeFileSync } from 'node:fs';
import path from 'node:path';
 
const __dirname = path.resolve();
const outputPath = path.join(__dirname, 'src/styles/font-fallbacks.css');
 
const { fontFaces, fontFamily } = createFontStack([openSans, arial, roboto]);
 
const css = `/* This file is generated using capsize. Run `pnpm vite-node capsize-font-fallbacks.ts` to refresh. */
 
:root {
  --font-family: ${fontFamily}, sans-serif;
}

${fontFaces}`;
 
writeFileSync(outputPath, css);
```

### Capsize Tooling

You need to add two Capsize packages to your project:

```shell
pnpm add -D @capsizecss/core @capsizecss/metrics
```

The first package provides `createFontStack`, which we use to create a fallback font CSS directive and some font-face CSS:

```typescript
const { fontFaces, fontFamily } = createFontStack(
  [openSans, arial, roboto]);
```

You can add `{fontFaceFormat: 'styleObject'}` as a second (options) parameter on `createFontStack` here, for output better suited to CSS-in-JS styling frameworks.

### Choosing Fallback Fonts

Typically, you will want to use Arial as a fallback font for sans-serif fonts, and Times New Roman for serif.  The Katie Hempenius article (mentioned above) notes, although these are quite safe fallbacks for desktop devices, neither is available on Android, so Roboto is a good second fallback.

You can import individual fonts from `@capsizecss/metrics`, as in lines `2`-`4`.  Then, combine them into a font stack array.  The code adds `sans-serif` as a third fallback in the output CSS.

### `vite-node`

You can run TypeScript node scripts in your Astro project by installing `vite-node` as a dev dependency.  To run `vite-node` (after install the package as a dependency), use:

```shell
pnpm vite-node capsize-font-fallbacks.ts
```

## 💅 Generated CSS

Running the script should create a `src/styles/font-fallbacks.css` file:

```css
/* This file is generated with capsize. Run `vite-node capsize-font-fallbacks.ts` to refresh. */
 
:root {
  --font-family: "Open Sans", "Open Sans Fallback: Arial", "Open Sans Fallback: Roboto", sans-serif;
}
 
@font-face {
  font-family: 'Open Sans Fallback: Arial';
  src: local('Arial');
  ascent-override: 101.1768%;
  descent-override: 27.7323%;
  size-adjust: 105.6416%
}
 
@font-face {
  font-family: 'Open Sans Fallback: Roboto';
  src: local('Roboto');
  ascent-override: 101.2887%;
  descent-override: 27.763%;
  size-adjust: 105.5249%
}
```

For the first time, you get to see these mysterious CSS font adjustments (`ascent-override`, `descent-override` and `size-adjust`)!

## 🏡 Homepage

The final missing part is the client code for the home page, which will include all the stylesheets we mentioned.  An alternative is to place those imports in a layout file, accessible to all site pages.

```astro
---
import '~styles/fonts.css';
import '~styles/global.css';
 
import '~styles/font-fallbacks.css';
---
 
<html lang="en-GB">
	<head>
		< !-- TRUNCATED... -->
	</head>
 
	<body>
		<main>
			<h1>🌟 Twinkle, Twinkle, Little Star</h1>
			<p>
				Twinkle, twinkle, little star,<br />
				How I wonder what you are! <br />
				Up above the world so high, <br />
				Like a diamond in the sky.
			</p>
			< !-- TRUNCATED... -->
		</main>
	</body>
</html>
```

## 💯 Astro Font Fallbacks: Frontend

When you have everything working, try commenting out the fallback CSS import line in your Astro page file to see the difference. You will probably need to throttle the network speed to see the load in detail.

I used Times New Roman as a fallback for a sans-serif font here, just to exaggerate the layout shift.  Generally, you will use opt for Arial as a safe sans-serif fallback.

## 🙌🏽 Astro Font Fallbacks: Wrapping Up

In this post, we saw a way to implement Astro font fallbacks.  In particular, we saw:

- why using a `font-display: swap` improves user experience;
- why you might use Capsize or **Fontaine** to reduce CLS; and
- how you can use **Capsize** to generate fallback CSS.


You can see the <a href="https://github.com/rodneylab/astro/tree/main/demos/astro-capsize-font-fallbacks">full code for this project in the Rodney Lab GitHub repo</a>.  I do hope you have found this post useful! Let me know if you decide to use Capsize in a production project, or if you preferred Fontaine.  Also, let me know about any possible improvements to the content above.

## 🙏🏽 Astro Font Fallbacks: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a href="https://twitter.com/messages/compose?recipient_id=1323579817258831875" aria-label="@ask Rodney">@askRodney</a> on Twitter, <a href="https://toot.community/@rodney">@rodney@toot.community</a> on Mastodon and also the <a aria-label="Contact Rodney Lab via Telegram" href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on Astro" href="https://rodneylab.com/tags/astro/">Astro</a> as well as <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/seo/">SEO</a>. Also, <a href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.