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.

  1. Install the package (variable fonts are preferred — a single file supports every weight):
pnpm --filter web add @fontsource-variable/poppins
  1. Swap the @import and update --font-sans-fallback in apps/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.

  1. Install both packages:
pnpm --filter web add @fontsource-variable/inter @fontsource-variable/playfair-display
  1. 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;
}
  1. Point the --font-heading token at your heading stack in apps/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.

  1. Place your font files under apps/web/public/fonts/. Supported formats: .woff2 (recommended), .woff, .ttf, .otf.

  2. Declare the faces in apps/web/src/styles/custom.css (or any CSS file imported from styles.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;
}
  1. Reference it in --font-sans-fallback in apps/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.

Modern SaaS (Clean and Professional)

pnpm --filter web add @fontsource-variable/inter
# Headings and body: Inter

Editorial (Content-Heavy Apps)

pnpm --filter web add @fontsource-variable/source-sans-3 @fontsource-variable/source-serif-4
# Body: Source Sans 3
# Headings: Source Serif 4

Startup (Friendly and Approachable)

pnpm --filter web add @fontsource-variable/dm-sans
# Headings and body: DM Sans

Technical (Developer Tools)

pnpm --filter web add @fontsource-variable/ibm-plex-sans @fontsource/ibm-plex-mono
# Body: IBM Plex Sans
# Code: IBM Plex Mono

Premium (Luxury/Finance)

pnpm --filter web add @fontsource-variable/outfit
# Headings and body: Outfit

Font Variable Reference

The font system uses CSS variables defined in two places:

VariableDefined InPurpose
--font-sans-fallbackstyles.cssSelf-hosted Fontsource (or local) font stack
--font-headingshadcn-ui.cssHeading font (if different)
--font-sansshadcn-ui.cssFinal 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:

  1. Check the Network tab in DevTools for font files loading
  2. Verify fonts render on both Mac and Windows
  3. Test with slow network throttling to see fallback behavior
  4. Run Lighthouse to check for font-related performance issues
# Quick check for font loading
pnpm dev
# Open DevTools > Network > Filter: Font
# Verify your custom font files are loading

Frequently Asked Questions

Why does my custom font not appear on Mac?
By default, Makerkit prioritizes Apple system fonts. Edit shadcn-ui.css and remove -apple-system and BlinkMacSystemFont from the --font-sans token to use your custom font on Apple devices.
How do I add a monospace font for code blocks?
Install a monospace Fontsource package (e.g. @fontsource-variable/jetbrains-mono), @import it in styles.css, and set the --font-mono token in shadcn-ui.css. Consider IBM Plex Mono, JetBrains Mono, or Fira Code.
Can I use variable fonts?
Yes. Prefer the @fontsource-variable/<name> packages — a single file supports every weight, often smaller than multiple static font files.
How do I improve font loading performance?
Prefer variable fonts, import only the weights you use for static packages, and rely on font-display: swap (the Fontsource default).

Next Steps