unifont
  • All fonts Browse or filter the whole catalogue
  • Compare providers One family, every CDN, side by side
  • Documentation Install, providers, custom providers
  • HTTP API The endpoints this site is built on

Providers

The seven built-in providers, and what each one can tell you.

A provider is a small adapter over one font source. createUnifont() takes them in order and asks each in turn until one answers.

ts

import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
  providers.google(),
  providers.bunny(),
  providers.fontshare(),
  providers.fontsource(),
  providers.googleicons(),
  providers.npm(),
])

What each one can do

Not every provider implements every method. listFonts() is optional, for example.

ProviderresolveFontgetFontPropertieslistFontsNeeds options
googleyesyesyesno
bunnyyesyesyesno
fontshareyesyesyesno
fontsourceyesyesyesno
googleiconsyesyesyesno
npmyesyesnono
adobeyesyesnoid

npm can't list, because the answer would be the whole registry. adobe can't, because Typekit ties everything to a project id.

Google

ts

providers.google()

The largest open library. It serves woff2 with a unicode-range per subset, which is why one weight of a Google family often comes back as a dozen faces, one per script.

Two experimental options are worth knowing about. experimental.glyphs asks for a subset containing only the characters you name. That's how you get a 2 kB file for a logo:

ts

providers.google({
  experimental: {
    glyphs: { Poppins: ['Hello', 'World'] },
  },
})

experimental.variableAxis gives you axes other than weight (slant, casual, cursive, mono) for families that publish them:

ts

providers.google({
  experimental: {
    variableAxis: {
      Recursive: { CASL: [['0', '1']], MONO: [['0', '1']] },
    },
  },
})

You can pass both per family too, through resolveFont's options.google, which wins over whatever the provider was created with.

OptionType
experimental.glyphs{ [family: string]: string[] }
experimental.variableAxis{ [family: string]: Partial<Record<VariableAxis, ([string, string] | string)[]>> }
options.google.experimental.glyphsstring[]
options.google.experimental.variableAxisPartial<Record<VariableAxis, ([string, string] | string)[]>>

Bunny

ts

providers.bunny()

The same library as Google, mirrored on fonts.bunny.net without the analytics. The family names match, so you can swap bunny in wherever a request to Google would be a privacy problem. The compare view shows where the two disagree on subsets.

Fontshare

ts

providers.fontshare()

Indian Type Foundry's library. Around a hundred families, free for commercial use, and better drawn than most free fonts.

Fontshare doesn't split by subset, so a family comes back as one file per weight and style, with no unicode-range at all.

Fontsource

ts

providers.fontsource()

Open fonts packaged for npm and served from jsDelivr. Useful if you want the Google library without touching Google's servers, or the exact files a lockfile would pin.

Google Icons

ts

providers.googleicons()

Material Symbols, as variable icon fonts. experimental.glyphs matters more here than anywhere else. The full Material Symbols file is huge, and naming the icons you use cuts it to almost nothing.

ts

providers.googleicons({
  experimental: {
    glyphs: {
      'Material Symbols Outlined': ['arrow_right', 'favorite', 'arrow_drop_down'],
    },
  },
})

As with Google, options.googleicons.experimental.glyphs narrows it to one family and wins. Either way it only works on the newer Material Symbols families.

OptionType
experimental.glyphs{ [family: string]: string[] }
options.googleicons.experimental.glyphsstring[]

npm

ts

providers.npm()

Resolves fonts from npm packages, either from node_modules or from a CDN. It reads your package.json dependencies and recognises @fontsource/*, @fontsource-variable/*, and other font packages it knows about.

In a build tool you usually want local resolution with the CDN fallback turned off, so nothing leaves the machine:

ts

import { access, readFile } from 'node:fs/promises'

providers.npm({
  readFile: path => readFile(path, 'utf-8').catch(() => null),
  exists: path => access(path).then(() => true).catch(() => false),
  remote: false,
})

With remote: false the sources come back as file:// URLs into node_modules, ready to copy or hash into your own build. Pass resolve for any layout where the package isn't linked under <root>/node_modules: pnpm's isolated store, hoisting to a monorepo root, Yarn PnP, or a bundler alias.

ts

import { fileURLToPath } from 'node:url'

providers.npm({
  readFile: path => readFile(path, 'utf-8').catch(() => null),
  resolve: id => fileURLToPath(import.meta.resolve(id)),
})
OptionTypeDefault
cdnstring'https://cdn.jsdelivr.net/npm'
remotebooleantrue
readFile(path: string) => Promise<string | null>none
exists(path: string) => Promise<boolean>falls back to readFile
resolve(id: string) => string | null | Promise<string | null>import.meta.resolve, then <root>/node_modules/<id>
rootstring'.'

Return null from resolve (or throw) when a package isn't installed, and the provider falls back to the CDN unless remote is false. Without exists, checking for a font file means reading and decoding all of it through readFile.

Any CDN that serves packages by path works for cdn, so providers.npm({ cdn: 'https://esm.sh' }) is fine.

Three more options apply to one family at a time:

Family optionTypeDefault
packagestringdetected from package.json, or inferred from the family name
versionstring'latest', and only used for CDN resolution
filestringthe per-weight and per-style entry points (<weight>.css, <weight>-italic.css), then index.css
ts

const { fonts } = await unifont.resolveFont('Roboto', {
  options: {
    npm: { package: '@fontsource/roboto', file: 'latin.css' },
  },
})

Adobe

ts

if (!process.env.TYPEKIT_ID) {
  throw new Error('TYPEKIT_ID is required to resolve Adobe fonts.')
}

providers.adobe({ id: process.env.TYPEKIT_ID })

Adobe Fonts, through one Typekit project id or several. The id is yours, so this site can't ask Adobe on your behalf. Families that only exist on Adobe are missing from the catalogue here, even though unifont can resolve them on your own machine.

id is required and takes a string or a string[]. Read it from the environment rather than writing it into your source.

Limiting a single call

Providers belong to the instance, but you can limit any one call to a few of them:

ts

const { fonts } = await unifont.resolveFont('Poppins', {}, ['bunny'])
const properties = await unifont.getFontProperties('Poppins', ['bunny'])

That's how the compare view asks every provider the same question and labels each answer.