Custom Fonts
Typography Variable Architecture
This design system decouples visual typography from component primitives. All text elements resolve font families through two standard CSS custom properties defined at document root:
--font-displayUsed for display elements (brand titles, major headings, hero text).
--font-bodyUsed for body copy, paragraphs, form controls, buttons, and UI chrome.
Approach 1 · Next.js App Router (Recommended)
For Next.js applications, load fonts using next/font/google in your root layout. Pass the CSS variable names directly into the HTML root element to eliminate layout shifts (CLS) and ensure zero runtime font downloads:
// app/layout.tsx
import { Inter, Fraunces } from "next/font/google";
const displayFont = Fraunces({
subsets: ["latin"],
variable: "--font-display",
});
const bodyFont = Inter({
subsets: ["latin"],
variable: "--font-body",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${displayFont.variable} ${bodyFont.variable}`}>
<body>{children}</body>
</html>
);
}Approach 2 · ThemeProvider Runtime Configurator
If your application uses the dynamic theme configuration engine, pass custom Google font names to ThemeProvider. The provider injects font assets dynamically and updates CSS variables at runtime:
import { ThemeProvider } from "@shavin/ui";
export default function App({ children }) {
return (
<ThemeProvider
accentColor="grass"
radius="medium"
displayFont="Fraunces"
bodyFont="Outfit"
>
{children}
</ThemeProvider>
);
}Approach 3 · Local Fonts & Global CSS
For custom local .woff2 font files or non-Next.js React applications, declare the font variables directly in your root stylesheet (global.css):
/* global.css */
@font-face {
font-family: "CustomBrandFont";
src: url("/fonts/custom-brand.woff2") format("woff2");
}
:root {
--font-display: "CustomBrandFont", serif;
--font-body: "Inter", sans-serif;
}