Typography โ
The official typeface: Inter โ
Lantern's one and only typeface is Inter, a sans-serif designed for user interfaces. It is the workhorse across the web app, the admin portal, and the marketing site. We do not pair it with a second display face; brand expression comes from weight, tracking, and scale, never from a competing typeface.
Creator and license โ
Inter is the work of Rasmus Andersson (known online as rsms), who began the project in 2016. It is open source and released under the SIL Open Font License 1.1, which is what lets us redistribute and self-host it.
- Project: https://github.com/rsms/inter
- Creator: https://rsms.me/
- License: SIL Open Font License 1.1 (see the
LICENSEfile inside the@fontsource-variable/interpackage, and https://openfontlicense.org/)
Per the OFL, the copyright notice and license travel with the font. Keep this attribution in place, and keep the bundled license in the font package, whenever the fonts are shipped.
How it is loaded โ
We self-host Inter. No request for the font ever leaves our origin, so no visitor IP is exposed to a third-party font CDN. That is a deliberate privacy choice and it matches the rest of the stack.
Inter is defined in exactly one place, the shared packages/ui/fonts.css, which every surface imports. That file owns both the @font-face import and the canonical font stack:
/* packages/ui/fonts.css (the single source) */
@import '@fontsource-variable/inter/wght.css';
:root {
--font-sans: 'Inter Variable', 'Inter', system-ui, ..., sans-serif;
}/* apps/web/src/styles.css, apps/admin/src/shared/styles/styles.css, and
apps/site/src/styles/base.css all do: */
@import '@lantern/ui/fonts.css';
body {
font-family: var(--font-sans);
}- The font is vendored via the
@fontsource-variable/internpm package, declared once inpackages/ui/package.json(not in each app). wght.cssis the roman (upright) build with a single variable weight axis, 100..900, so every weight we use comes from one file.- Each unicode subset (latin, latin-ext, cyrillic, greek, vietnamese) declares its own
unicode-range, so a browser downloads only the subset a page actually renders. An English page pulls just the latin woff2. - At build time Vite fingerprints the woff2 into each app's own output, served same-origin. No request for the font ever reaches a third-party font CDN.
- The family
@fontsourceemits is named'Inter Variable';--font-sansleads with it and keeps plainInteras a fallback for a locally-installed static Inter, then the system sans. Usevar(--font-sans), never re-list the stack.
Because the import lives in apps/web/src/styles.css, which Storybook's preview also loads, components render in Inter inside Storybook exactly as in the app. See the Foundations/Typography story for a live specimen and a self-check that the variable font is actually active.
Keeping it up to date โ
The font follows upstream rsms/inter releases through the npm registry. To pull a newer Inter:
npm update @fontsource-variable/inter --workspace packages/uiThe version is pinned in package-lock.json, so updates are explicit and reproducible rather than tracking a moving branch.
Where it is wired โ
- Single source:
packages/ui/fonts.cssholds the@fontsourceimport and the--font-sanstoken;@fontsource-variable/interis a dependency ofpackages/uionly. - apps/web and Storybook:
apps/web/src/styles.cssdoes@import '@lantern/ui/fonts.css', and Storybook's preview loads that stylesheet. - apps/admin:
apps/admin/src/shared/styles/styles.cssimports the shared module; every admin font stack usesvar(--font-sans). Previously it named Inter but loaded nothing, so it fell back to a system font. - apps/site:
apps/site/src/styles/base.cssimports the shared module. Its old hand-placed latin subset (public/fonts/) and static preload are gone. A build-time Vite plugin (preloadInterLatininapps/site/vite.config.mjs) looks up the fingerprinted latin woff2 in the build bundle and injects a<link rel="preload">at the start of<head>(respecting the resolvedbase), so the marketing page keeps its early-font fetch with no hand-maintained path to rot. - PWA:
apps/web's service-worker precache (globPatternsinapps/web/vite.config.mjs) includeswoff2, so the font is cached for offline launches instead of falling back to a system font. - docs (VitePress): bundles its own Inter via the default theme. Leave it.
Follow-ups โ
- CSP: now that Inter is fully self-hosted, the
https://fonts.gstatic.com(font-src) andhttps://fonts.googleapis.com(style-src) allowances inapps/web/public/_headersare unused and can be dropped in a focused CSP-hardening pass.