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

Resolving fonts

Weights, styles, subsets and formats, and the bit that catches people out.

resolveFont() takes a family, some options, and optionally a list of providers to try.

ts

const { fonts, fallbacks, provider } = await unifont.resolveFont('Poppins', {
  weights: ['400', '700'],
  styles: ['normal', 'italic'],
  subsets: ['latin', 'latin-ext'],
  formats: ['woff2'],
})

Every option is a hint, not a guarantee you'll get back exactly that. For example, if you ask for otf from a provider that only serves woff2, you'll get woff2.

Weights

Default: ['400'].

Discrete weights are strings. A variable range is one string with a space: '100 900'.

ts

await unifont.resolveFont('Poppins', { weights: ['300', '500 900'] })

If no variable font covers the range you asked for, unifont won't expand that range into every published weight. It picks a few: the weights at each end, plus whichever is nearest 400, so body text still looks right. '500 900' on a static family gives you two or three faces, not five.

If no published weight falls inside the range at all ('450 480' on a family that ships 400 and 500) you get the nearest weight rather than nothing.

List the weights you want if you need the ones in between:

ts

await unifont.resolveFont('Poppins', { weights: ['500', '600', '700', '800', '900'] })

Styles

Default: ['normal', 'italic'].

ts

await unifont.resolveFont('Poppins', { styles: ['normal'] })

Worth narrowing. The default doubles the number of files, for a page that may never use italic.

Subsets

Default: ['cyrillic-ext', 'cyrillic', 'greek-ext', 'greek', 'vietnamese', 'latin-ext', 'latin'].

ts

await unifont.resolveFont('Poppins', { subsets: ['latin'] })

Subsets make the biggest difference to how much you send, and they're the easiest to get wrong. Drop latin-ext and you lose characters that Polish, Czech and Turkish need. Every family page on this site shows a coverage check against sample text, so you can see what narrower subsets cost.

Providers that don't split by subset, like Fontshare and most of npm, ignore this option and return whole files with no unicode-range.

Formats

Default: ['woff2'].

ts

await unifont.resolveFont('Poppins', { formats: ['woff2', 'woff'] })

Every browser has supported woff2 for years. Only ask for more if you know why you need it.

Per-family provider options

Some providers accept options for a single family. They're grouped by provider name, and typed from the providers you passed to createUnifont():

ts

const { fonts } = await unifont.resolveFont('Poppins', {
  options: {
    google: {
      experimental: { glyphs: ['Hello', 'World'] },
    },
  },
})

What comes back

ts

interface FontFaceData {
  src: Array<{ name: string } | { url: string, format?: string, tech?: string }>
  display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
  weight?: string | number | [number, number]
  stretch?: string
  style?: string
  unicodeRange?: string[]
  featureSettings?: string
  variationSettings?: string
  meta?: { priority?: number, subset?: string, init?: RequestInit }
}

A src entry with name is a local font. One with url is remote. meta.subset is the provider's own name for the range.

meta.init carries a RequestInit if the provider needs headers to fetch the file. Pass it through if you're downloading the font rather than linking to it.