# fontaine

> Automatic font fallback based on font metrics

Latest version **0.8.1** (published 2026-08-21) · MIT license · 0 weekly downloads

## Install

```sh
npm install fontaine
pnpm add fontaine
yarn add fontaine
bun add fontaine
```

## Health

**Score 55/100 (C)** — status: active.

Positive: no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; no esm support; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.8.1 |
| Published | 2026-08-21 |
| First published | 2022-09-28 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 1988 |
| Author | Daniel Roe |
| Maintainers | danielroe |
| Keywords | fonts, cls, web-vitals, performance |

## Links

- npm: https://www.npmjs.com/package/fontaine
- Repository: https://github.com/unjs/fontaine
- Homepage: https://github.com/unjs/fontaine#readme
- Issues: https://github.com/unjs/fontaine/issues
- npm.io page: https://npm.io/package/fontaine

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 0.8.1 (latest) — 2026-08-21
- 0.8.0 — 2026-01-14
- 0.7.0 — 2025-11-06
- 0.6.0 — 2025-04-22
- 0.5.0 — 2024-03-07
- 0.4.1 — 2023-09-10
- 0.4.0 — 2023-06-08
- 0.3.1 — 2023-03-17
- 0.3.0 — 2023-03-05
- 0.2.3 — 2022-11-01
- 0.2.1 — 2022-10-19
- 0.2.0 — 2022-10-17
- 0.1.3 — 2022-10-16
- 0.1.2 — 2022-10-16
- 0.1.1 — 2022-09-29
- … 3 more at https://npm.io/package/fontaine/versions

## README

# fontaine

[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![Github Actions][github-actions-src]][github-actions-href]
[![Codecov][codecov-src]][codecov-href]

> Automatic font fallback based on font metrics

- [✨ &nbsp;Changelog](https://github.com/unjs/fontaine/blob/main/CHANGELOG.md)
- [▶️ &nbsp;Online playground](https://stackblitz.com/github/unjs/fontaine/tree/main/packages/fontaine/playground)

## Features

- 💪 Reduces CLS by using local font fallbacks with crafted font metrics.
- ✨ Generates font metrics and overrides automatically.
- ⚡️ Pure CSS, zero runtime overhead.

On the playground project, enabling/disabling `fontaine` makes the following difference rendering `/`, with no customisation required:

|             | Before | After   |
| ----------- | ------ | ------- |
| CLS         | `0.24` | `0.054` |
| Performance | `92`   | `100`   |

## Installation

With `pnpm`

```bash
pnpm add -D fontaine
```

Or, with `npm`

```bash
npm install -D fontaine
```

Or, with `yarn`

```bash
yarn add -D fontaine
```

## Usage

```js
import { FontaineTransform } from 'fontaine'

// Astro config - astro.config.mjs
import { defineConfig } from 'astro/config'

const options = {
  // You can specify fallbacks as an array (applies to all fonts)
  fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],

  // Or as an object to configure specific fallbacks per font family
  // fallbacks: {
  //   Poppins: ['Helvetica Neue'],
  //   'JetBrains Mono': ['Courier New']
  // },

  // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
  resolvePath: id => `file:///path/to/public/dir${id}`,
  // fallbackName: (originalName) => `${name} fallback`
  // sourcemap: false
  // skipFontFaceGeneration: (fallbackName) => fallbackName === 'Roboto fallback'
}

// Vite
export default {
  plugins: [FontaineTransform.vite(options)]
}

// Next.js
export default {
  webpack(config) {
    config.plugins = config.plugins || []
    config.plugins.push(FontaineTransform.webpack(options))
    return config
  },
}

// Docusaurus plugin - to be provided to the plugins option of docusaurus.config.js
// n.b. you'll likely need to require fontaine rather than importing it
const fontaine = require('fontaine')

function fontainePlugin(_context, _options) {
  return {
    name: 'fontaine-plugin',
    configureWebpack(_config, _isServer) {
      return {
        plugins: [
          fontaine.FontaineTransform.webpack(options),
        ],
      }
    },
  }
}

// Gatsby config - gatsby-node.js
const { FontaineTransform } = require('fontaine')

exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
  const config = getConfig()
  config.plugins.push(FontaineTransform.webpack(options))
  actions.replaceWebpackConfig(config)
}

export default defineConfig({
  integrations: [],
  vite: {
    plugins: [
      FontaineTransform.vite({
        fallbacks: ['Arial'],
        resolvePath: id => new URL(`./public${id}`, import.meta.url), // id is the font src value in the CSS
      }),
    ],
  },
})
```

> **Note**
> If you are using Nuxt, check out [nuxt-font-metrics](https://github.com/danielroe/nuxt-font-metrics) which uses `fontaine` under the hood.

If your custom font is used through the mechanism of CSS variables, you'll need to make a tweak to your CSS variables to give fontaine a helping hand. Docusaurus is an example of this, it uses the `--ifm-font-family-base` variable to reference a custom font. In order that fontaine can connect the variable with the font, we need to add a `{Name of Font} fallback` suffix to that variable. What does this look like? Well imagine we were using the custom font Poppins which is referenced from the `--ifm-font-family-base` variable, we'd make the following adjustment:

```diff
:root {
  /* ... */
-  --ifm-font-family-base: 'Poppins';
+  --ifm-font-family-base: 'Poppins', 'Poppins fallback';
```

Behind the scenes, there is a 'Poppins fallback' `@font-face` rule that has been created by fontaine. By manually adding this fallback font family to our CSS variable, we make our site use the fallback `@font-face` rule with the correct font metrics that fontaine generates.

## Using Sass, Less or Stylus

The bundler plugin runs before your preprocessor, so it only sees the raw source of files your bundler treats as modules. If your `@font-face` rules live in a Sass partial, are generated by a mixin, or are pulled in with a Sass `@import`, fontaine never sees them: Sass inlines all of that before the bundler does.

For these setups, use the PostCSS plugin instead. It runs over fully-compiled CSS, so `@font-face` rules and `font-family` values are already plain CSS by the time fontaine reads them.

```js
// postcss.config.js
module.exports = {
  plugins: [
    require('fontaine/postcss')({
      fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
    }),
    // ...any other plugins, such as autoprefixer or cssnano
  ],
}
```

It accepts the same `fallbacks`, `categoryFallbacks`, `resolvePath`, `skipFontFaceGeneration` and `fallbackName` options as the bundler plugin. Do not use both plugins at once.

A couple of things worth checking in a webpack setup:

- Enable source maps on `sass-loader`. Sass does not rebase the URLs it inlines, so fontaine uses the source map to work out which stylesheet a `src` URL was written relative to. Without it, relative font paths are resolved against the entrypoint and metrics cannot be read.
- Set `importLoaders` on `css-loader` (`2` for `postcss-loader` + `sass-loader`) if you import CSS that contains `@font-face` rules, such as Fontsource packages. Otherwise `css-loader` resolves those imports without running them through `postcss-loader`, and fontaine never sees them.

> [!NOTE]
> Because a `font-family` declaration and the `@font-face` rule it refers to may end up in different stylesheets, the PostCSS plugin appends a fallback to every non-generic `font-family` it finds, whether or not it generated the matching `@font-face` rule itself. A fallback family with no `@font-face` rule is ignored by the browser.

## Category-Aware Fallbacks

Fontaine automatically selects appropriate fallback fonts based on font categories (serif, sans-serif, monospace, etc.) when using object-based fallback configuration.

```js
const options = {
  // Use an empty object to enable automatic category-based fallbacks
  fallbacks: {},

  // Or customize specific categories while keeping defaults for others
  categoryFallbacks: {
    'serif': ['Georgia', 'Times New Roman'],
    'sans-serif': ['Arial', 'Helvetica'],
    // monospace, display, and handwriting categories use defaults
  }
}
```

### Default Category Fallbacks

- **sans-serif**: `BlinkMacSystemFont`, `Segoe UI`, `Helvetica Neue`, `Arial`, `Noto Sans`
- **serif**: `Times New Roman`, `Georgia`, `Noto Serif`
- **monospace**: `Courier New`, `Roboto Mono`, `Noto Sans Mono`
- **display** & **handwriting**: Same as sans-serif

> **Note:** These presets are available programmatically via `DEFAULT_CATEGORY_FALLBACKS` and can be used with the `resolveCategoryFallbacks` helper function for advanced use cases. Both are exported from the `fontaine` package and shared across related packages (e.g., `fontless`) to ensure consistent fallback behavior.

### Fallback Priority

1. **Array format** (`fallbacks: ['Arial']`) - Uses specified fonts for all families (legacy behavior)
2. **Per-family override** (`fallbacks: { Poppins: ['Arial'] }`) - Uses specified fonts for that family
3. **Category-based** - When a family isn't specified, uses the appropriate category preset
4. **Global default** - Falls back to sans-serif preset if no category is detected

Example:

```js
{
  fallbacks: {
    // Specific override for Poppins
    'Poppins': ['Arial'],
    // Other sans-serif fonts will use the sans-serif preset
    // Serif fonts will use the serif preset automatically
  },
  categoryFallbacks: {
    // Customize the serif preset
    'serif': ['Georgia']
  }
}
```

## How it works

`fontaine` will scan your `@font-face` rules and generate fallback rules with the correct metrics. For example:

```css
@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff')
      format('woff');
  font-weight: 700;
}
/* This additional font-face declaration will be added to your CSS. */
@font-face {
  font-family: 'Roboto fallback';
  src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Helvetica Neue'),
      local('Arial'), local('Noto Sans');
  ascent-override: 92.7734375%;
  descent-override: 24.4140625%;
  line-gap-override: 0%;
}
```

Then, whenever you use `font-family: 'Roboto'`, `fontaine` will add the fallback to the font-family:

```css
:root {
  font-family: 'Roboto';
  /* This becomes */
  font-family: 'Roboto', 'Roboto fallback';
}
```

## 💻 Development

- Clone this repository
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` (use `npm i -g corepack` for Node.js < 16.10)
- Install dependencies using `pnpm install`
- Run interactive tests using `pnpm dev`; launch a vite server using source code with `pnpm demo:dev`

## Credits

This would not have been possible without:

- amazing tooling and generated metrics from [capsizecss](https://seek-oss.github.io/capsize/)
- suggestion and algorithm from [Katie Hempenius](https://katiehempenius.com/) & [Kara Erickson](https://github.com/kara) on the Google Aurora team - see [notes on calculating font metric overrides](https://docs.google.com/document/d/e/2PACX-1vRsazeNirATC7lIj2aErSHpK26hZ6dA9GsQ069GEbq5fyzXEhXbvByoftSfhG82aJXmrQ_sJCPBqcx_/pub)
- package name suggestion from [**@clemcode**](https://github.com/clemcode)

## License

Made with ❤️

Published under [MIT License](./LICENCE).

<!-- Badges -->

[npm-version-src]: https://npmx.dev/api/registry/badge/version/fontaine
[npm-version-href]: https://npmx.dev/package/fontaine
[npm-downloads-src]: https://npmx.dev/api/registry/badge/downloads/fontaine
[npm-downloads-href]: https://npmx.dev/package/fontaine
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/unjs/fontaine/ci.yml?branch=main&style=flat-square
[github-actions-href]: https://github.com/unjs/fontaine/actions/workflows/ci.yml
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/fontaine/main?style=flat-square
[codecov-href]: https://codecov.io/gh/unjs/fontaine

---
_Source: https://npm.io/package/fontaine · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
