@molecule/app-fonts
Core interface · fonts · App (browser) · v1.2.1 · Apache-2.0
Font loading and typography — 20 self-hosted families ship as provider bonds (check here before npm-installing a webfont)
npm install @molecule/app-fontsHow it works
@molecule/app-fonts is the fonts core interface on the app (browser) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 20 providers: @molecule/app-fonts-arimo, @molecule/app-fonts-dm-sans, @molecule/app-fonts-dm-serif-display, @molecule/app-fonts-ibm-plex-mono, @molecule/app-fonts-ibm-plex-sans, @molecule/app-fonts-inter, @molecule/app-fonts-ionicons, @molecule/app-fonts-jetbrains-mono, @molecule/app-fonts-lato, @molecule/app-fonts-lora, @molecule/app-fonts-manrope, @molecule/app-fonts-material-symbols, @molecule/app-fonts-merriweather, @molecule/app-fonts-newsreader, @molecule/app-fonts-playfair-display, @molecule/app-fonts-plus-jakarta-sans, @molecule/app-fonts-source-sans-3, @molecule/app-fonts-source-serif-4, @molecule/app-fonts-space-grotesk, @molecule/app-fonts-system.
import { setFont } from '@molecule/app-fonts'
import { font as sans } from '@molecule/app-fonts-inter'
import { font as mono } from '@molecule/app-fonts-jetbrains-mono'
setFont(sans) // once, at app startup — before first paint
setFont(mono)Providers (20): @molecule/app-fonts-arimo, @molecule/app-fonts-dm-sans, @molecule/app-fonts-dm-serif-display, @molecule/app-fonts-ibm-plex-mono, @molecule/app-fonts-ibm-plex-sans, @molecule/app-fonts-inter, @molecule/app-fonts-ionicons, @molecule/app-fonts-jetbrains-mono, @molecule/app-fonts-lato, @molecule/app-fonts-lora, @molecule/app-fonts-manrope, @molecule/app-fonts-material-symbols, @molecule/app-fonts-merriweather, @molecule/app-fonts-newsreader, @molecule/app-fonts-playfair-display, @molecule/app-fonts-plus-jakarta-sans, @molecule/app-fonts-source-sans-3, @molecule/app-fonts-source-serif-4, @molecule/app-fonts-space-grotesk, @molecule/app-fonts-system
Works with: @molecule/app-bond
Reference
Font interfaces and provider system for molecule.dev.
Registers a font per role (sans, serif, mono) and applies it app-wide:
setFont() bonds the definition, sets the --mol-font-{role} CSS variable,
and injects the loading CSS (<link> for CDN sources, @font-face for
local files) into the document head. Font bond packages (e.g.
@molecule/app-fonts-inter, @molecule/app-fonts-jetbrains-mono) export a
ready-made FontDefinition named font.
Before adding a font, check the font bonds — the fleet ships 20 families.
The providers under @molecule/app-fonts-* cover arimo, dm-sans,
dm-serif-display, ibm-plex-mono, ibm-plex-sans, inter, ionicons,
jetbrains-mono, lato, lora, manrope, material-symbols, merriweather,
newsreader, playfair-display, plus-jakarta-sans, source-sans-3, source-serif-4,
space-grotesk and system. source-sans-3 IS Source Sans Pro (Adobe
renamed the typeface), so a stylesheet that asks for 'Source Sans Pro' is
served by @molecule/app-fonts-source-sans-3. Do NOT npm install a webfont
package (fontsource, typeface-*, a family's npm package such as
source-sans-pro) for a family on this list: the bond is the font and wiring
it is one setFont(font) call, while an install in the sandbox re-resolves
the whole tree (~90 s) and nests node_modules at the workspace root, so a
relative path from the app directory misses it.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setFont } from '@molecule/app-fonts'
import { font as sans } from '@molecule/app-fonts-inter'
import { font as mono } from '@molecule/app-fonts-jetbrains-mono'
setFont(sans) // once, at app startup — before first paint
setFont(mono)
Type
core
Installation
npm install @molecule/app-fonts @molecule/app-bond
API
Interfaces
FontFaceDescriptor
Descriptor for a single @font-face declaration (local fonts).
interface FontFaceDescriptor {
/**
* Font file: a filename relative to the package's fontsDir
* (e.g., 'Arimo-Regular.ttf'), or an already-final URL — a root-absolute
* asset path (`/assets/Arimo-1a2b.ttf`), a full `http(s):` URL or a
* `data:` URL — which `setFont()` uses as-is. That is the shape a bundler
* emits for `import ttf from '…Arimo-Regular.ttf?url'`.
*/
file: string
/** CSS font-weight value — number (400) or range string ('100 1000'). */
weight: number | string
/** CSS font-style: 'normal' or 'italic'. Defaults to 'normal'. */
style?: string
/** If true, this is a variable font — wrap in `@supports` (font-variation-settings: normal). */
variable?: boolean
}
FontSource
Font loading source information.
interface FontSource {
/** The source type for loading. */
type: FontSourceType
/** Full URL for the font CSS stylesheet (for google/bunny/custom). */
url?: string
/** Preconnect origins (e.g., ['https://fonts.googleapis.com', 'https://fonts.gstatic.com']). */
preconnect?: string[]
/** `@font-face` descriptors for local font files. */
faces?: FontFaceDescriptor[]
}
FontDefinition
Complete definition for a single font family.
interface FontDefinition {
/** CSS font-family name (e.g., 'Arimo', 'Inter'). */
family: string
/** Which role this font fills (sans, serif, or mono). */
role: FontRole
/** Fallback fonts appended after the family in the CSS value. */
fallbacks: string[]
/** How to load this font. */
source: FontSource
/**
* Extra CSS injected verbatim after this font's `@font-face` block — the
* utility class an icon font needs (e.g. `.material-symbols-outlined { … }`).
* Only meaningful for `icon`-role fonts; ignored for text roles.
*/
utilityCss?: string
}
SetFontOptions
Options for setFont().
interface SetFontOptions {
/**
* The public base path the app is served under — Vite's `base`
* (`import.meta.env.BASE_URL`), `/blog/` for a site under a sub-path.
* Local faces are loaded from `<basePath>fonts/<file>`. When omitted the
* document's `<base href>`, then `import.meta.env.BASE_URL`, then `/` apply.
*/
basePath?: string
}
FontConfig
Resolved font configuration for all three roles.
interface FontConfig {
sans: FontDefinition
serif: FontDefinition
mono: FontDefinition
}
Types
FontSourceType
How a font is loaded.
type FontSourceType = 'google' | 'bunny' | 'local' | 'system' | 'custom'
FontRole
Which font role this font fills.
sans/serif/mono are text roles applied app-wide via --mol-font-{role}.
icon is for icon fonts (e.g. Material Symbols) — consumed through a utility
class (.material-symbols-outlined), not a text font-family, so an icon
font carries utilityCss that setFont() injects alongside its @font-face.
type FontRole = 'sans' | 'serif' | 'mono' | 'icon'
Functions
resolveFontBasePath(basePath)
Resolves the public base path local font files are served under, always
with a leading and a trailing slash (/, /blog/).
Order: the explicit basePath → the document's <base href> → Vite's
import.meta.env.BASE_URL (the base option, present in Vite-built and
Vite-served code) → /. A site served from a sub-path (base: '/blog/')
has its public/fonts/ at /blog/fonts/, so a root-absolute /fonts/…
404s there and the fallback stack renders instead.
function resolveFontBasePath(basePath?: string): string
basePath— An explicit base, e.g.import.meta.env.BASE_URLor/blog.
Returns: The normalized base path.
isAbsoluteFontFileUrl(file)
Whether a local face's file is already a final URL, so setFont() must use
it as-is instead of resolving it under <basePath>fonts/.
True for a root-absolute path (/assets/ionicons-1a2b.woff), a full URL
(https:, http:, //host/…) or an inline data: URL. Those are exactly
the shapes a bundler emits for import woff from '…woff?url'.
function isAbsoluteFontFileUrl(file: string): boolean
file— The face'sfilefield.
Returns: true when the value is already a URL.
resolveFontFaceUrl(file, basePath)
Resolves a local face's file to the URL its @font-face loads from.
An already-final URL is returned unchanged (including a Vite asset URL that
already carries the configured base). A plain filename is served from
<basePath>fonts/<file>.
function resolveFontFaceUrl(file: string, basePath?: string): string
file— The face'sfilefield.basePath— Public base path fromresolveFontBasePath(default/).
Returns: The URL to put in src: url(…).
setFont(font, options)
Registers a font definition for a specific role (sans, serif, or mono).
Bonds the font via the named provider system, sets the
--mol-font-{role} CSS variable, and injects font loading
into the document head (CDN <link> tags or @font-face declarations).
function setFont(font: FontDefinition, options?: SetFontOptions): void
font— The font definition including family, role, and source configuration.options—basePath: the public base the app is served under (passimport.meta.env.BASE_URLin a Vite app); local faces load from<basePath>fonts/<file>. SeeresolveFontBasePathfor the default.
getFont(role)
Retrieves the font definition bonded for a specific role.
function getFont(role: FontRole): FontDefinition | undefined
role— The font role ('sans','serif', or'mono').
Returns: The bonded font definition, or undefined if none is set.
hasFont(role)
Checks whether a font has been bonded for a specific role.
function hasFont(role: FontRole): boolean
role— The font role to check.
Returns: true if a font is bonded for the given role.
getFontConfig()
Returns the complete font configuration for all three roles. Falls back to system font stacks for any role not explicitly set.
function getFontConfig(): FontConfig
Returns: The resolved font config with sans, serif, and mono definitions.
hasFontConfig()
Checks whether any font role has been explicitly configured.
function hasFontConfig(): boolean
Returns: true if at least one font role is bonded.
resetFonts()
Removes all bonded font definitions. Primarily used in tests.
function resetFonts(): void
buildFontFamily(font)
Build a CSS font-family value from a FontDefinition.
function buildFontFamily(font: FontDefinition): string
font— The font definition containing a primary family and fallback list.
Returns: A CSS-ready font-family string (e.g. "'Arimo', system-ui, sans-serif").
Constants
systemSans
System sans-serif font stack (used when no provider is configured).
const systemSans: FontDefinition
systemSerif
System serif font stack (used when no provider is configured).
const systemSerif: FontDefinition
systemMono
System monospace font stack (used when no provider is configured).
const systemMono: FontDefinition
Available Providers
| Provider | Package |
|---|---|
| Arimo | @molecule/app-fonts-arimo |
| DM Sans | @molecule/app-fonts-dm-sans |
| DM Serif Display | @molecule/app-fonts-dm-serif-display |
| IBM Plex Mono | @molecule/app-fonts-ibm-plex-mono |
| IBM Plex Sans | @molecule/app-fonts-ibm-plex-sans |
| Inter | @molecule/app-fonts-inter |
| Ionicons | @molecule/app-fonts-ionicons |
| JetBrains Mono | @molecule/app-fonts-jetbrains-mono |
| Lato | @molecule/app-fonts-lato |
| Lora | @molecule/app-fonts-lora |
| Manrope | @molecule/app-fonts-manrope |
| Material Symbols Outlined | @molecule/app-fonts-material-symbols |
| Merriweather | @molecule/app-fonts-merriweather |
| Newsreader | @molecule/app-fonts-newsreader |
| Playfair Display | @molecule/app-fonts-playfair-display |
| Plus Jakarta Sans | @molecule/app-fonts-plus-jakarta-sans |
| Source Sans Pro (Source Sans 3) | @molecule/app-fonts-source-sans-3 |
| Source Serif 4 | @molecule/app-fonts-source-serif-4 |
| Space Grotesk | @molecule/app-fonts-space-grotesk |
| System | @molecule/app-fonts-system |
Injection Notes
Requirements
Peer dependencies:
@molecule/app-bond^1.0.1
Runtime Dependencies
-
@molecule/app-bond -
ALL fonts are local — NEVER load one from a CDN. Every provider bond here (
@molecule/app-fonts-inter,-jetbrains-mono,-material-symbols, and the display/serif faces) issource: 'local'and self-hosts from/fonts/. Do NOT add a Google Fonts<link href="https://fonts.googleapis.com…">, an@import url(https://fonts.gstatic…), or anext/font/googleimport: external fonts fail in the sandbox (egress is blocked), leak the visitor's IP, and add render-blocking round-trips (cross-site CDN caching died with browser cache partitioning ~2020). If an imported app loads fonts from a CDN, localize them — swap to one of these packages, or bundle the file + a local@font-face. -
Consume fonts through the CSS variable (
var(--mol-font-sans)etc.), never a hardcodedfont-family— the variable is the swap point; hardcoding defeats the bond. The app's theme/ClassMap layer normally references it already. -
Local-source fonts load from
<basePath>fonts/<file>unless the face'sfileis already a URL. Afilestarting with/,//,httpordata:is used as-is — that is what a bundler's asset import produces (import woff from '@molecule/app-fonts-ionicons/fonts/ionicons.woff?url'), so an app that cannot copy files intopublic/can still bond a font package. Every local font provider package ships a./fonts/*export for exactly that import. Any otherfileis a filename that must be served at<basePath>fonts/(copy the bond'sfonts/into the app'spublic/fonts/) or every face silently 404s and the fallback stack renders instead. CDN sources inject<link>tags (with preconnect origins). -
A site served under a sub-path (Vite
base: '/blog/',BASE_PATH) MUST pass the base:setFont(font, { basePath: import.meta.env.BASE_URL }). Itspublic/fonts/is at/blog/fonts/, so a root-absolute/fonts/…404s on every page — and an e2e console guard fails every test on that 404. Without the optionsetFont()falls back to the document's<base href>, thenimport.meta.env.BASE_URLwhere the bundler defines it, then/; passing it is what makes the path certain in both dev and the built site. -
Call
setFont()at startup, before first paint, to avoid a font flash; calling again for the same role replaces the font (idempotent per role). -
getFontConfig()always returns a complete config — roles never explicitly set fall back to system stacks (systemSans/systemSerif/systemMono). -
Icon fonts (role
icon, e.g.@molecule/app-fonts-material-symbols) are local like any other font — never load them from a CDN. They carry autilityCssstring (the.material-symbols-outlinedclass) thatsetFont()injects with the@font-face, so the icon glyphs work fully offline. NEVER add a<link href="https://fonts.googleapis.com/...Material+Symbols...">— it fails in the sandbox (blocked egress) and leaks the visitor's IP. -
DOM injection is skipped outside the browser, so
setFont()is SSR-safe.