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:
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
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)
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:
// 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:
Comment out the current import
Uncomment the desired pairing import
Update the className to use the new pairing’s variables
Method 2: Environment-Based Selection
Use environment variables to select pairings:
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:
NEXT_PUBLIC_FONT_PAIRING = minimal
Method 3: User-Selectable Themes
Allow users to switch fonts dynamically:
"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:
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:
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:
/* 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:
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:
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
Import Individual Fonts
Import only the fonts you need. // 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" ;
Apply Custom Combination
Create your own pairing object. 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 >
);
}
Override CSS Variables
Point the variables to your chosen fonts. @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:
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:
/* Editorial: Generous spacing for reading */
.editorial h1 { font-size : 2.5 rem ; line-height : 1.2 ; }
.editorial p { line-height : 1.75 ; }
/* Minimal: Compact and efficient */
.minimal h1 { font-size : 2 rem ; line-height : 1.25 ; }
.minimal p { line-height : 1.6 ; }
/* Impact: Large and bold */
.impact h1 { font-size : 4 rem ; line-height : 1 ; }
.impact p { line-height : 1.5 ; }
Best Practices
Limit the Number of Pairings
Don’t install too many pairings. 2-4 is usually enough. Each pairing adds to your bundle size.
Maintain Consistency
If using multiple pairings, ensure they’re used consistently across similar contexts. Don’t randomly switch fonts.
Test Performance
Monitor page load times when adding multiple pairings. Each font adds HTTP requests and increases bundle size.
Document Your Choices
Comment your code to explain why you’re using specific pairings in specific contexts.
Consider Users
If allowing users to switch fonts, persist their choice in localStorage and respect their preference across sessions.
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:
Verify font variables are applied to the correct element (usually <html>)
Check that CSS variables in globals.css point to the right fonts
Clear browser cache and reload
Restart your dev server
CSS Variables Conflicting
If multiple pairings conflict:
Use scoped classes instead of global :root variables
Ensure each section has unique class names
Check specificity in DevTools to see which styles are winning
If page load is slow with multiple pairings:
Reduce the number of installed pairings
Only load fonts needed for the current page
Use font subsetting to reduce file sizes
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.