Customize Application Fonts
Configure custom fonts using Google Fonts, local fonts, or system fonts in your Makerkit application with Tanstack Start font optimization.
Customize your application's typography by editing the CSS in apps/web/src/styles.css and the font tokens in apps/web/src/styles/shadcn-ui.css. Fonts are self-hosted via Fontsource packages — installed from npm and imported directly in CSS — for privacy and performance, with no runtime font-loading component.
By default, Makerkit uses Apple's system font on Apple devices (San Francisco) and falls back to Inter (via @fontsource-variable/inter) on other platforms.
How Fonts Are Wired
There is no fonts.ts module. The font system is entirely CSS-based and lives in two files:
apps/web/src/styles.css imports the self-hosted font package and declares the fallback font stack that the design tokens map onto:
/* Sans font (self-hosted via Fontsource, weights 300–700) */@import '@fontsource-variable/inter';/* The sans font stack the design tokens map onto. */:root { --font-sans-fallback: 'Inter Variable', system-ui, 'Helvetica Neue', Helvetica, Arial, sans-serif;}apps/web/src/styles/shadcn-ui.css maps that fallback into the Tailwind @theme inline tokens:
@theme inline { --font-sans: -apple-system, BlinkMacSystemFont, var(--font-sans-fallback); --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; --font-heading: var(--font-sans);}Quick Font Change
Replace the default Inter font with any Google Font using its Fontsource package.
- Install the package (variable fonts are preferred — a single file supports every weight):
pnpm --filter web add @fontsource-variable/poppins- Swap the
@importand update--font-sans-fallbackinapps/web/src/styles.css:
@import '@fontsource-variable/poppins';:root { --font-sans-fallback: 'Poppins Variable', system-ui, 'Helvetica Neue', Helvetica, Arial, sans-serif;}That's it — every component that consumes --font-sans now renders in Poppins.
Using Different Fonts for Headings and Body
Create visual hierarchy by using different fonts for headings and body text.
- Install both packages:
pnpm --filter web add @fontsource-variable/inter @fontsource-variable/playfair-display- Import both and declare a heading fallback variable in
apps/web/src/styles.css:
@import '@fontsource-variable/inter';@import '@fontsource-variable/playfair-display';:root { --font-sans-fallback: 'Inter Variable', system-ui, 'Helvetica Neue', Helvetica, Arial, sans-serif; --font-heading-fallback: 'Playfair Display Variable', Georgia, 'Times New Roman', serif;}- Point the
--font-headingtoken at your heading stack inapps/web/src/styles/shadcn-ui.css:
@theme inline { --font-sans: -apple-system, BlinkMacSystemFont, var(--font-sans-fallback); --font-heading: var(--font-heading-fallback);}Using Local Fonts
For fonts not available on Fontsource, or for complete control over the font files, use a plain CSS @font-face declaration.
Place your font files under
apps/web/public/fonts/. Supported formats:.woff2(recommended),.woff,.ttf,.otf.Declare the faces in
apps/web/src/styles/custom.css(or any CSS file imported fromstyles.css):
@font-face { font-family: 'CustomFont'; src: url('/fonts/CustomFont-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap;}@font-face { font-family: 'CustomFont'; src: url('/fonts/CustomFont-Medium.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap;}@font-face { font-family: 'CustomFont'; src: url('/fonts/CustomFont-Bold.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap;}- Reference it in
--font-sans-fallbackinapps/web/src/styles.css:
:root { --font-sans-fallback: 'CustomFont', system-ui, 'Helvetica Neue', Helvetica, Arial, sans-serif;}Removing Apple System Font Default
By default, Makerkit prioritizes Apple's system font on macOS and iOS for a native feel. To use your chosen font consistently across all platforms, edit the @theme inline block in apps/web/src/styles/shadcn-ui.css:
@theme inline { /* Remove -apple-system and BlinkMacSystemFont to use your font everywhere */ --font-sans: var(--font-sans-fallback); --font-heading: var(--font-sans);}This ensures your custom font displays on Apple devices instead of San Francisco.
Popular Font Combinations
Modern SaaS (Clean and Professional)
pnpm --filter web add @fontsource-variable/inter# Headings and body: InterEditorial (Content-Heavy Apps)
pnpm --filter web add @fontsource-variable/source-sans-3 @fontsource-variable/source-serif-4# Body: Source Sans 3# Headings: Source Serif 4Startup (Friendly and Approachable)
pnpm --filter web add @fontsource-variable/dm-sans# Headings and body: DM SansTechnical (Developer Tools)
pnpm --filter web add @fontsource-variable/ibm-plex-sans @fontsource/ibm-plex-mono# Body: IBM Plex Sans# Code: IBM Plex MonoPremium (Luxury/Finance)
pnpm --filter web add @fontsource-variable/outfit# Headings and body: OutfitFont Variable Reference
The font system uses CSS variables defined in two places:
| Variable | Defined In | Purpose |
|---|---|---|
--font-sans-fallback | styles.css | Self-hosted Fontsource (or local) font stack |
--font-heading | shadcn-ui.css | Heading font (if different) |
--font-sans | shadcn-ui.css | Final font stack with system fallbacks |
Tailwind consumes these through the @theme inline block:
@theme inline { --font-sans: -apple-system, BlinkMacSystemFont, var(--font-sans-fallback); --font-heading: var(--font-sans);}Optimizing Font Loading
Prefer Variable Fonts
Fontsource ships variable font packages (@fontsource-variable/<name>) that pack every weight into a single file, usually smaller than multiple static weights combined:
/* One import covers 300–700 and everything in between */@import '@fontsource-variable/inter';Import Only the Weights You Use
For static (non-variable) Fontsource packages, import only the weights you actually need instead of the whole family:
/* Avoid importing the entire family */@import '@fontsource/roboto/400.css';@import '@fontsource/roboto/500.css';@import '@fontsource/roboto/700.css';Use font-display: swap
Fontsource packages already default to font-display: swap, showing a fallback immediately and swapping when the font loads. For local @font-face declarations, set font-display: swap yourself (as shown above) to avoid invisible text (FOIT).
Common Mistakes
Loading too many font weights: Each static weight adds to bundle size. Prefer a single variable-font import, or only import the weights you use (typically 400, 500, 600, 700).
Forgetting to update the token: After changing the @import in styles.css, update --font-sans-fallback (and --font-heading in shadcn-ui.css if you use a separate heading font), otherwise the tokens still point at the old family.
Using font-display: block: This causes invisible text until fonts load (FOIT). Use swap for better perceived performance.
Not testing on Windows: Apple system fonts don't exist on Windows. Always test your fallback fonts on non-Apple devices.
Verification
After updating fonts:
- Check the Network tab in DevTools for font files loading
- Verify fonts render on both Mac and Windows
- Test with slow network throttling to see fallback behavior
- Run Lighthouse to check for font-related performance issues
# Quick check for font loadingpnpm dev# Open DevTools > Network > Filter: Font# Verify your custom font files are loadingFrequently Asked Questions
Why does my custom font not appear on Mac?
How do I add a monospace font for code blocks?
Can I use variable fonts?
How do I improve font loading performance?
Next Steps
- Back to Customization Overview
- Configure your theme colors to complement your typography
- Set up your layout style for navigation
- Update your application logo