# Reducing layout shift

> Use font metrics to build a fallback that takes up the same space as the real font.

In most cases, a webfont arrives after the first paint, so text is first set in whatever comes next in the stack. If that family is a different size, the page reflows when the real font lands. [`fontaine`](https://github.com/unjs/fontaine) solves that issue: it generates a `@font-face` rule that points at a locally installed font and scales it with `size-adjust`, `ascent-override`, `descent-override` and `line-gap-override` until it occupies the same space as the real one.

`unifont` and `fontaine` are designed to work together. `unifont` resolves the location of the font and its descriptors; `fontaine` turns those into the fallback rule.

## Let an integration do it

If you want fonts to work rather than to wire this up yourself, use an integration. Each of these resolves with `unifont` and generates the fallback ahead of time, so nothing runs in the browser.

In any Vite app (React, Svelte, Solid, Vue), add [`fontless`](https://github.com/unjs/fontaine/tree/main/packages/fontless):

```ts
// vite.config.ts
import react from '@vitejs/plugin-react'
import { fontless } from 'fontless'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [react(), fontless()],
})
```

Then name a family in your CSS. The plugin scans for it, resolves it through `unifont`, and injects the `@font-face` rules and the metric-matched fallback:

```css
h1 {
  font-family: "Poppins", sans-serif;
}
```

In Nuxt, [`@nuxt/fonts`](https://fonts.nuxt.com) does the same thing. You can enable it like this:

```bash
npx nuxt add fonts
```

If you'd rather not add a plugin at all, [Leturgerð](https://leturgero.web-runes.dev) is a CLI that downloads the files once and writes the `@font-face` rules and the fallback into your project:

```bash
npx @leturgero/cli@latest
```

The result is plain font and CSS files that you commit, so it works with any tool rather than one bundler.

## Build it yourself

If you're generating the CSS yourself, there are three steps: resolve the family, read the metrics of the file you're going to serve, and generate the fallback face.

```ts
import { generateFontFace, readMetrics } from 'fontaine'
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([providers.google()])
const { fonts, fallbacks } = await unifont.resolveFont('Poppins')

const url = fonts
  .flatMap(font => font.src)
  .find(source => 'url' in source)?.url

const metrics = url ? await readMetrics(url) : null

const css = metrics
  ? generateFontFace(metrics, { name: 'Poppins fallback', font: fallbacks[0] ?? 'sans-serif' })
  : ''
```

`generateFontFace()` expands the generic family for you: `local("Arial"), local("Arimo"), …` for `sans-serif`, so the rule matches something on most platforms. `resolveFont()` returns `fallbacks` for this reason. It's the generic the provider reports as closest to the family.

Put the fallback between the real family and the generic, so text is set in the adjusted face until the webfont loads:

```css
h1 {
  font-family: "Poppins", "Poppins fallback", sans-serif;
}
```

### Which metrics to use

`readMetrics()` downloads the font file and measures it, which costs a request per family. Two cheaper sources exist, with a tradeoff each:

- `getMetricsForFamily(family)` from `fontaine` looks the name up in a bundled dataset. No network, but the dataset covers the family as published, which may differ from the cut a provider serves.
- The `metrics` on a resolved [`FontFaceData`](/docs/reference#fontfacedata), where the provider reports them. No network and no guessing, but few providers do, and the set is partial: `generateFontFace()` needs `xWidthAvg` to compute `size-adjust`, and a provider that omits it leaves the fallback unscaled.

Whichever you pick, cache the result. Metrics for a given URL never change, so they belong in the same [storage](/docs/caching) as the rest of your font data.

### When a fallback won't help

The adjusted face is built on `local()`, so it only applies if one of the named fonts is installed. Where nothing matches, the browser skips the rule and falls through to the generic, unadjusted. That's the usual outcome on a machine with no Microsoft or Apple system fonts, so treat the fallback as an improvement on most visits rather than a guarantee on all of them.

## Next steps

- [Resolving fonts](/docs/resolving) covers the options that control which faces you get back.
- [Caching](/docs/caching) covers where to keep resolved metadata between builds.
