Skip to main content
Fonttrio allows you to install multiple pairings in the same project, making it easy to switch between different font combinations or use different pairings for different sections of your application.

Why Mix Pairings?

Installing multiple pairings is useful for:
  • A/B testing different typography styles before committing
  • Multi-section apps where different areas need different moods (e.g., marketing landing page + documentation + app dashboard)
  • Theme switching to let users choose their preferred reading experience
  • Custom combinations by mixing fonts from different pairings

Installing Multiple Pairings

You can install as many pairings as you want. Each pairing adds its fonts to the registry/ directory:
1

Install First Pairing

Start with your primary pairing.
npx shadcn@latest add https://www.fonttrio.xyz/r/editorial.json
This installs:
  • registry/fonts/playfair-display.tsx
  • registry/fonts/source-serif-4.tsx
  • registry/fonts/jetbrains-mono.tsx
  • registry/pairings/editorial.tsx
2

Install Second Pairing

Add another pairing for comparison or mixing.
npx shadcn@latest add https://www.fonttrio.xyz/r/minimal.json
This adds:
  • registry/fonts/inter.tsx (new)
  • registry/fonts/jetbrains-mono.tsx (skipped, already exists)
  • registry/pairings/minimal.tsx (new)
3

Install Additional Pairings

Keep adding pairings as needed.
npx shadcn@latest add https://www.fonttrio.xyz/r/corporate.json
npx shadcn@latest add https://www.fonttrio.xyz/r/impact.json
Shared fonts (like JetBrains Mono, which appears in many pairings) are only installed once. Subsequent pairings will reuse the existing font file.

Switching Between Pairings

Once you have multiple pairings installed, you can switch between them in your layout:

Method 1: Direct Import Swap

The simplest way is to change which pairing you import:
app/layout.tsx
// Switch this import to change the active pairing
import { editorial } from "@/registry/pairings/editorial";
// import { minimal } from "@/registry/pairings/minimal";
// import { corporate } from "@/registry/pairings/corporate";

import "./globals.css";

export default function RootLayout({ children }) {
  return (
    <html 
      className={`
        ${editorial.heading.variable} 
        ${editorial.body.variable} 
        ${editorial.mono.variable}
      `}
    >
      <body>{children}</body>
    </html>
  );
}
To switch pairings:
  1. Comment out the current import
  2. Uncomment the desired pairing import
  3. Update the className to use the new pairing’s variables

Method 2: Environment-Based Selection

Use environment variables to select pairings:
app/layout.tsx
import { editorial } from "@/registry/pairings/editorial";
import { minimal } from "@/registry/pairings/minimal";
import { corporate } from "@/registry/pairings/corporate";
import "./globals.css";

const pairings = {
  editorial,
  minimal,
  corporate,
};

const activePairing = pairings[
  (process.env.NEXT_PUBLIC_FONT_PAIRING as keyof typeof pairings) || "editorial"
];

export default function RootLayout({ children }) {
  return (
    <html 
      className={`
        ${activePairing.heading.variable} 
        ${activePairing.body.variable} 
        ${activePairing.mono.variable}
      `}
    >
      <body>{children}</body>
    </html>
  );
}
Set in .env.local:
.env.local
NEXT_PUBLIC_FONT_PAIRING=minimal

Method 3: User-Selectable Themes

Allow users to switch fonts dynamically:
app/providers.tsx
"use client";

import { createContext, useContext, useState, ReactNode } from "react";
import { editorial } from "@/registry/pairings/editorial";
import { minimal } from "@/registry/pairings/minimal";
import { corporate } from "@/registry/pairings/corporate";

type PairingName = "editorial" | "minimal" | "corporate";

const pairings = { editorial, minimal, corporate };

const FontContext = createContext<{
  pairing: PairingName;
  setPairing: (name: PairingName) => void;
}>({
  pairing: "editorial",
  setPairing: () => {},
});

export function FontProvider({ children }: { children: ReactNode }) {
  const [pairing, setPairing] = useState<PairingName>("editorial");
  const activePairing = pairings[pairing];

  return (
    <FontContext.Provider value={{ pairing, setPairing }}>
      <div 
        className={`
          ${activePairing.heading.variable} 
          ${activePairing.body.variable} 
          ${activePairing.mono.variable}
        `}
      >
        {children}
      </div>
    </FontContext.Provider>
  );
}

export const useFontPairing = () => useContext(FontContext);
Use in layout:
app/layout.tsx
import { FontProvider } from "./providers";
import "./globals.css";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <FontProvider>
          {children}
        </FontProvider>
      </body>
    </html>
  );
}
Create a font switcher component:
components/font-switcher.tsx
"use client";

import { useFontPairing } from "@/app/providers";

export function FontSwitcher() {
  const { pairing, setPairing } = useFontPairing();

  return (
    <div className="flex gap-2">
      <button
        onClick={() => setPairing("editorial")}
        className={pairing === "editorial" ? "font-bold" : ""}
      >
        Editorial
      </button>
      <button
        onClick={() => setPairing("minimal")}
        className={pairing === "minimal" ? "font-bold" : ""}
      >
        Minimal
      </button>
      <button
        onClick={() => setPairing("corporate")}
        className={pairing === "corporate" ? "font-bold" : ""}
      >
        Corporate
      </button>
    </div>
  );
}
User-selectable fonts can cause a flash of unstyled content (FOUC) during hydration. Consider storing the preference in localStorage and applying it before the initial render.

Using Different Pairings in Different Sections

You can apply different pairings to different parts of your app:

Method 1: Section-Specific Classes

Import all pairings and apply their variables to specific sections:
app/layout.tsx
import { editorial } from "@/registry/pairings/editorial";
import { minimal } from "@/registry/pairings/minimal";
import { impact } from "@/registry/pairings/impact";
import "./globals.css";

export default function RootLayout({ children }) {
  return (
    <html 
      className={`
        ${editorial.heading.variable} 
        ${editorial.body.variable} 
        ${editorial.mono.variable}
        ${minimal.heading.variable}
        ${minimal.body.variable}
        ${impact.heading.variable}
        ${impact.body.variable}
      `}
    >
      <body>{children}</body>
    </html>
  );
}
Create section-specific CSS:
app/globals.css
/* Default to Editorial for blog content */
@layer base {
  :root {
    --font-heading: var(--font-playfair-display);
    --font-body: var(--font-source-serif-4);
    --font-mono: var(--font-jetbrains-mono);
  }
}

/* Use Minimal for dashboard */
.dashboard {
  --font-heading: var(--font-inter);
  --font-body: var(--font-inter);
}

/* Use Impact for landing page */
.landing {
  --font-heading: var(--font-bebas-neue);
  --font-body: var(--font-barlow);
}
Apply to sections:
<div className="dashboard">
  <h1>Dashboard</h1>  {/* Uses Inter */}
  <p>Analytics data</p>
</div>

<div className="landing">
  <h1>Welcome</h1>  {/* Uses Bebas Neue */}
  <p>Get started</p>
</div>

Method 2: Route-Based Pairings

Apply different pairings to different route groups:
app/(blog)/layout.tsx
import { editorial } from "@/registry/pairings/editorial";

export default function BlogLayout({ children }) {
  return (
    <div className={`
      ${editorial.heading.variable} 
      ${editorial.body.variable} 
      ${editorial.mono.variable}
    `}>
      {children}
    </div>
  );
}
app/(dashboard)/layout.tsx
import { minimal } from "@/registry/pairings/minimal";

export default function DashboardLayout({ children }) {
  return (
    <div className={`
      ${minimal.heading.variable} 
      ${minimal.body.variable} 
      ${minimal.mono.variable}
    `}>
      {children}
    </div>
  );
}

Creating Custom Combinations

Mix and match individual fonts from different pairings:
1

Install Source Pairings

Install pairings that contain the fonts you want.
npx shadcn@latest add https://www.fonttrio.xyz/r/editorial.json
npx shadcn@latest add https://www.fonttrio.xyz/r/minimal.json
npx shadcn@latest add https://www.fonttrio.xyz/r/impact.json
2

Import Individual Fonts

Import only the fonts you need.
app/layout.tsx
// Heading from Impact pairing
import { bebasNeue } from "@/registry/fonts/bebas-neue";
// Body from Minimal pairing
import { inter } from "@/registry/fonts/inter";
// Mono from Editorial pairing
import { jetbrainsMono } from "@/registry/fonts/jetbrains-mono";
3

Apply Custom Combination

Create your own pairing object.
app/layout.tsx
const customPairing = {
  heading: bebasNeue,
  body: inter,
  mono: jetbrainsMono,
};

export default function RootLayout({ children }) {
  return (
    <html className={`
      ${customPairing.heading.variable} 
      ${customPairing.body.variable} 
      ${customPairing.mono.variable}
    `}>
      <body>{children}</body>
    </html>
  );
}
4

Override CSS Variables

Point the variables to your chosen fonts.
app/globals.css
@layer base {
  :root {
    --font-heading: var(--font-bebas-neue);
    --font-body: var(--font-inter);
    --font-mono: var(--font-jetbrains-mono);
  }
}
When mixing fonts, ensure they work well together. Consider contrast (serif vs sans), weight compatibility, and x-height. Test thoroughly with real content.

Common Use Cases

Marketing Site + Documentation

Use a bold pairing for the landing page and a readable pairing for docs:
// Landing page uses Impact pairing
<div className="landing">
  <h1>Bold Marketing Message</h1>
</div>

// Docs use Editorial pairing
<div className="docs">
  <h1>Documentation Title</h1>
  <p>Detailed technical content...</p>
</div>

Multi-Brand Application

Different brands within the same app:
const brandPairings = {
  brandA: editorial,
  brandB: minimal,
  brandC: corporate,
};

function BrandSection({ brand, children }) {
  const pairing = brandPairings[brand];
  
  return (
    <div className={`
      ${pairing.heading.variable}
      ${pairing.body.variable}
      ${pairing.mono.variable}
    `}>
      {children}
    </div>
  );
}

A/B Testing Fonts

Randomly assign pairings for testing:
const pairings = [editorial, minimal, corporate];
const randomPairing = pairings[Math.floor(Math.random() * pairings.length)];

// Or use a proper A/B testing framework
const pairing = useABTest('font-test', {
  control: editorial,
  variant: minimal,
});

Seasonal Themes

Switch pairings based on time of year:
function getSeasonalPairing() {
  const month = new Date().getMonth();
  
  if (month >= 10 || month <= 1) return cozyWinterPairing;
  if (month >= 2 && month <= 4) return freshSpringPairing;
  if (month >= 5 && month <= 7) return brightSummerPairing;
  return warmAutumnPairing;
}

const pairing = getSeasonalPairing();

Managing Multiple Pairings

Keep Track of Installed Pairings

Maintain a list of your active pairings:
lib/pairings.ts
import { editorial } from "@/registry/pairings/editorial";
import { minimal } from "@/registry/pairings/minimal";
import { corporate } from "@/registry/pairings/corporate";
import { impact } from "@/registry/pairings/impact";

export const PAIRINGS = {
  editorial,
  minimal,
  corporate,
  impact,
} as const;

export type PairingName = keyof typeof PAIRINGS;

export function getPairing(name: PairingName) {
  return PAIRINGS[name];
}

Load Fonts on Demand

For performance, only load fonts when needed:
import dynamic from "next/dynamic";

const EditorialFonts = dynamic(() => 
  import("@/registry/pairings/editorial").then(m => m.editorial)
);

const MinimalFonts = dynamic(() => 
  import("@/registry/pairings/minimal").then(m => m.minimal)
);
Dynamic font loading can cause layout shifts. This approach is only recommended for user-selectable fonts, not for initial page load.

Update Typography Scale per Pairing

Each pairing may need different CSS adjustments:
app/globals.css
/* Editorial: Generous spacing for reading */
.editorial h1 { font-size: 2.5rem; line-height: 1.2; }
.editorial p { line-height: 1.75; }

/* Minimal: Compact and efficient */
.minimal h1 { font-size: 2rem; line-height: 1.25; }
.minimal p { line-height: 1.6; }

/* Impact: Large and bold */
.impact h1 { font-size: 4rem; line-height: 1; }
.impact p { line-height: 1.5; }

Best Practices

1

Limit the Number of Pairings

Don’t install too many pairings. 2-4 is usually enough. Each pairing adds to your bundle size.
2

Maintain Consistency

If using multiple pairings, ensure they’re used consistently across similar contexts. Don’t randomly switch fonts.
3

Test Performance

Monitor page load times when adding multiple pairings. Each font adds HTTP requests and increases bundle size.
4

Document Your Choices

Comment your code to explain why you’re using specific pairings in specific contexts.
5

Consider Users

If allowing users to switch fonts, persist their choice in localStorage and respect their preference across sessions.

Performance Considerations

Font Loading Impact

Each pairing typically includes 3 fonts. If you install 3 pairings, you could have up to 9 fonts (though duplicates are shared).
// Editorial: 3 fonts
// Minimal: 2 new fonts (shares mono)
// Corporate: 2 new fonts (shares mono)
// Total: 7 unique fonts loaded

Optimize Font Loading

Use next/font optimizations:
import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",        // Prevent invisible text
  preload: true,          // Preload critical fonts
  fallback: ["system-ui"], // Fallback fonts
});

Subset Fonts

Only load required character sets:
const font = Font({
  subsets: ["latin"],  // Not latin-ext, cyrillic, etc.
  weight: ["400", "700"],  // Only weights you use
});

Troubleshooting

Fonts Not Switching

If changing pairings doesn’t update fonts:
  1. Verify font variables are applied to the correct element (usually <html>)
  2. Check that CSS variables in globals.css point to the right fonts
  3. Clear browser cache and reload
  4. Restart your dev server

CSS Variables Conflicting

If multiple pairings conflict:
  1. Use scoped classes instead of global :root variables
  2. Ensure each section has unique class names
  3. Check specificity in DevTools to see which styles are winning

Performance Issues

If page load is slow with multiple pairings:
  1. Reduce the number of installed pairings
  2. Only load fonts needed for the current page
  3. Use font subsetting to reduce file sizes
  4. Consider using a variable font instead of multiple weights

Next Steps

Customize Typography

Adjust typography scales for each pairing.

Use with shadcn/ui

Apply mixed pairings to shadcn/ui components.

Build docs developers (and LLMs) love