Skip to content

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.

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:

css
/* packages/ui/fonts.css (the single source) */
@import '@fontsource-variable/inter/wght.css';
:root {
  --font-sans: 'Inter Variable', 'Inter', system-ui, ..., sans-serif;
}
css
/* 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/inter npm package, declared once in packages/ui/package.json (not in each app).
  • wght.css is 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 @fontsource emits is named 'Inter Variable'; --font-sans leads with it and keeps plain Inter as a fallback for a locally-installed static Inter, then the system sans. Use var(--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:

bash
npm update @fontsource-variable/inter --workspace packages/ui

The 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.css holds the @fontsource import and the --font-sans token; @fontsource-variable/inter is a dependency of packages/ui only.
  • apps/web and Storybook: apps/web/src/styles.css does @import '@lantern/ui/fonts.css', and Storybook's preview loads that stylesheet.
  • apps/admin: apps/admin/src/shared/styles/styles.css imports the shared module; every admin font stack uses var(--font-sans). Previously it named Inter but loaded nothing, so it fell back to a system font.
  • apps/site: apps/site/src/styles/base.css imports the shared module. Its old hand-placed latin subset (public/fonts/) and static preload are gone. A build-time Vite plugin (preloadInterLatin in apps/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 resolved base), so the marketing page keeps its early-font fetch with no hand-maintained path to rot.
  • PWA: apps/web's service-worker precache (globPatterns in apps/web/vite.config.mjs) includes woff2, 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) and https://fonts.googleapis.com (style-src) allowances in apps/web/public/_headers are unused and can be dropped in a focused CSP-hardening pass.

Built with VitePress