useFonts
Loads web fonts on demand. Only the faces actually chosen hit the network, and the registry ships eighteen of them.
Import
ts
import { useFonts } from '@core/fonts/useFonts'Signature
ts
function useFonts(): {
loadFont: (name: string) => Promise<void>
unloadFont: (name: string) => void
addCustomFont: (config: FontDefinition) => void
setActiveFonts: (slots: { primary?: string, arabic?: string, mono?: string }) => Promise<void>
isLoaded: (name: string) => boolean
}Returns
| Member | Type | Description |
|---|---|---|
setActiveFonts | (slots) => Promise<void> | The main API. Loads each named face and writes the matching --sa-font-{slot} variable. |
loadFont | (name) => Promise<void> | Injects the stylesheet link and resolves once document.fonts.ready does. |
unloadFont | (name) => void | Removes the link and forgets the face. |
addCustomFont | (config) => void | Adds a FontDefinition to the registry. Ignored if the name is already there. |
isLoaded | (name) => boolean | document.fonts.check('1em "Name"'). false with no document. |
FontDefinition
| Key | Type | Description |
|---|---|---|
name | string | Display name, and the lookup key for every function here. |
family | string | The full CSS font-family value, written into the variable verbatim. It has to carry a fallback. |
weights | number[] | Translated to the wght@ axis on a Google Fonts URL. |
type | 'google' | 'local' | google builds the URL from the name and weights; local requires url. |
url | string | Override for google, required for local. |
script | 'latin' | 'arabic' | 'both' | What the pickers filter on. |
The three slots
| Slot | Variable | Default |
|---|---|---|
primary | --sa-font-primary | Inter |
arabic | --sa-font-arabic | Cairo |
mono | --sa-font-mono | JetBrains Mono |
The registry ships eighteen faces: ten Latin sans, three mono, and five that cover Arabic.
Example
vue
<script setup lang="ts">
import { useFonts } from '@core/fonts/useFonts'
const { setActiveFonts, addCustomFont, isLoaded } = useFonts()
addCustomFont({
name: 'Söhne',
family: '"Söhne", system-ui, sans-serif',
weights: [400, 600],
type: 'local',
url: '/fonts/soehne.css',
script: 'latin',
})
async function apply() {
await setActiveFonts({ primary: 'Söhne', mono: 'Fira Code' })
}
</script>
<template>
<div class="flex items-center gap-3">
<button type="button" @click="apply()">
Apply fonts
</button>
<span v-if="isLoaded('Fira Code')">Fira Code is ready.</span>
</div>
</template>Notes
- Deduped twice. A name already loaded returns immediately, and before injecting, the head is checked for a link with that exact href. Two racing callers cannot end up with two
<link>tags. loadFontawaitsdocument.fonts.ready, so a caller that awaits it can measure text without catching the fallback face.- An unknown name logs a warning through
console.warnand returns. It does not throw, so one bad name in a config cannot stop a boot. setActiveFontsloads the named faces in parallel and resolves once all of them have. It writes each variable as soon as the face is found in the registry, so the fallback infamilyis what paints until the file arrives.addCustomFontmutates the shared registry, so the face then appears in the font pickers too.- Everything that touches the DOM no-ops without a
document.
Related
useConfig—config.fontsholds the chosen names and any custom faces.- Theming — where the font variables are read.
- Internationalization — why the Arabic slot is separate.