Getting started
Install unifont, resolve a family, and turn the answer into CSS.
unifont is ESM-only, and has no peer dependencies.
npm i unifontCreate an instance
createUnifont() takes an array of providers. Order matters. They're tried in turn, and the first one that knows the family wins.
import { createUnifont, providers } from 'unifont'
const unifont = await createUnifont([
providers.google(),
providers.fontshare(),
providers.fontsource(),
])Ask what a family has
getFontProperties() tells you what a family publishes before you resolve anything. It returns undefined if no provider recognises the name.
const properties = await unifont.getFontProperties('Newsreader')
properties?.weights // ['200 800'], a variable range
properties?.styles // ['normal', 'italic']
properties?.subsets // ['cyrillic', 'greek', 'latin', 'latin-ext', 'vietnamese']
properties?.provider // 'google'A missing field means the provider doesn't publish that information.
Variable families report a range as one '<min> <max>' string, rather than listing every weight.
Resolve the faces
const { fonts, fallbacks, provider } = await unifont.resolveFont('Newsreader', {
weights: ['400', '600'],
styles: ['normal'],
subsets: ['latin'],
})fonts is an array of @font-face descriptors: src, weight, style, unicodeRange, and a meta.subset label if the provider gave one.
fallbacks holds the generic family the provider suggests (serif, sans-serif). (That's what you need if you're generating metric-adjusted fallbacks.)
Turn it into CSS
unifont stops at the data. Writing it out takes a few lines, and doing it yourself means the CSS comes out the way you want it:
const css = fonts.map((face) => {
const src = face.src
.map(source => 'name' in source
? `local("${source.name}")`
: `url("${source.url}")${source.format ? ` format("${source.format}")` : ''}`)
.join(', ')
return [
'@font-face {',
` font-family: "Newsreader";`,
` src: ${src};`,
face.weight ? ` font-weight: ${Array.isArray(face.weight) ? face.weight.join(' ') : face.weight};` : '',
` font-style: ${face.style ?? 'normal'};`,
` font-display: ${face.display ?? 'swap'};`,
face.unicodeRange ? ` unicode-range: ${face.unicodeRange.join(', ')};` : '',
'}',
].filter(Boolean).join('\n')
}).join('\n\n')Or don't bother. Every family page on this site serves the same CSS at /api/v1/fonts/<family>/css, and the API reference lists the parameters.
Errors
If a provider fails, unifont logs it and carries on, so one dead API doesn't break your build. Pass throwOnError if you'd rather it stopped:
const unifont = await createUnifont([providers.google()], { throwOnError: true })