Fonttrio is designed specifically for shadcn/ui projects. This guide shows you how to apply font pairings to shadcn/ui components, customize component typography, and follow best practices for a cohesive design system.
Why Fonttrio Works with shadcn/ui
Fonttrio uses the same distribution system as shadcn/ui:
Registry-based installation via the shadcn CLI
Next.js + Tailwind as the foundation
CSS variables for theming and customization
Copy-paste components that you fully own and can modify
Because both use CSS variables, fonts integrate seamlessly with shadcn/ui’s theming system.
Prerequisites
Before integrating Fonttrio with shadcn/ui:
Install shadcn/ui
Set up shadcn/ui in your Next.js project.
Install a Fonttrio Pairing
Add your chosen font pairing. npx shadcn@latest add https://www.fonttrio.xyz/r/editorial.json
Apply Fonts to Layout
Add font variables to your root layout. import { editorial } from "@/registry/pairings/editorial" ;
import "./globals.css" ;
export default function RootLayout ({ children }) {
return (
< html className = { `
${ editorial . heading . variable }
${ editorial . body . variable }
${ editorial . mono . variable }
` } >
< body > { children } </ body >
</ html >
);
}
Applying Fonts to Components
Once fonts are installed, they automatically apply to shadcn/ui components through CSS variables.
Typography Components
Heading and text components inherit fonts from the typography scale:
import { Button } from "@/components/ui/button" ;
import { Card , CardHeader , CardTitle , CardContent } from "@/components/ui/card" ;
export function Example () {
return (
< Card >
< CardHeader >
< CardTitle > Card Title </ CardTitle > { /* Uses --font-heading */ }
</ CardHeader >
< CardContent >
< p > Card content text </ p > { /* Uses --font-body */ }
</ CardContent >
</ Card >
);
}
Buttons use body font by default:
import { Button } from "@/components/ui/button" ;
< Button > Click me </ Button > { /* Uses --font-body */ }
Override with heading font for emphasis:
< Button className = "font-[family-name:var(--font-heading)]" >
Bold CTA
</ Button >
Input fields, labels, and form elements use body font:
import { Input } from "@/components/ui/input" ;
import { Label } from "@/components/ui/label" ;
import { Textarea } from "@/components/ui/textarea" ;
< div >
< Label htmlFor = "email" > Email </ Label > { /* Uses --font-body */ }
< Input id = "email" type = "email" /> { /* Uses --font-body */ }
< Textarea placeholder = "Message" /> { /* Uses --font-body */ }
</ div >
Code Components
Code blocks and inline code use monospace font:
import { Badge } from "@/components/ui/badge" ;
< code className = "font-[family-name:var(--font-mono)]" >
npm install
</ code >
< pre className = "font-[family-name:var(--font-mono)]" >
< code > const example = true; </ code >
</ pre >
If you’ve installed a Fonttrio pairing, the global CSS already applies --font-mono to <code> and <pre> elements, so you don’t need to add classes manually.
Customizing Component Typography
You can customize how fonts apply to specific components.
Modify Component Files
Since shadcn/ui components are copied into your project, you can edit them directly:
const Card = React . forwardRef < HTMLDivElement , CardProps >(
({ className , ... props }, ref ) => (
< div
ref = { ref }
className = { cn (
"rounded-lg border bg-card text-card-foreground shadow-sm" ,
"font-[family-name:var(--font-body)]" , // Explicitly set body font
className
) }
{ ... props }
/>
)
)
const CardTitle = React . forwardRef < HTMLParagraphElement , CardTitleProps >(
({ className , ... props }, ref ) => (
< h3
ref = { ref }
className = { cn (
"text-2xl font-semibold leading-none tracking-tight" ,
"font-[family-name:var(--font-heading)]" , // Explicitly set heading font
className
) }
{ ... props }
/>
)
)
Create Font-Aware Variants
Add font variants to component APIs using cva:
import { cva , type VariantProps } from "class-variance-authority" ;
const buttonVariants = cva (
"inline-flex items-center justify-center..." ,
{
variants: {
variant: {
default: "bg-primary text-primary-foreground..." ,
outline: "border border-input..." ,
},
size: {
default: "h-10 px-4 py-2" ,
sm: "h-9 px-3" ,
lg: "h-11 px-8" ,
},
font: {
body: "font-[family-name:var(--font-body)]" ,
heading: "font-[family-name:var(--font-heading)]" ,
mono: "font-[family-name:var(--font-mono)]" ,
},
},
defaultVariants: {
variant: "default" ,
size: "default" ,
font: "body" ,
},
}
);
Use in components:
< Button font = "heading" > Bold CTA </ Button >
< Button font = "mono" > Technical Action </ Button >
Create Typography Utilities
Define reusable typography classes:
@layer utilities {
.font-heading {
font-family : var ( --font-heading );
}
.font-body {
font-family : var ( --font-body );
}
.font-mono {
font-family : var ( --font-mono );
}
}
Or extend Tailwind config:
module . exports = {
theme: {
extend: {
fontFamily: {
heading: "var(--font-heading)" ,
body: "var(--font-body)" ,
mono: "var(--font-mono)" ,
},
},
},
}
Use in components:
< h1 className = "font-heading" > Heading </ h1 >
< p className = "font-body" > Body text </ p >
< code className = "font-mono" > Code </ code >
Component-Specific Examples
Accordion
import { Accordion , AccordionItem , AccordionTrigger , AccordionContent } from "@/components/ui/accordion" ;
< Accordion type = "single" collapsible >
< AccordionItem value = "item-1" >
< AccordionTrigger className = "font-heading text-lg" >
Styled with heading font
</ AccordionTrigger >
< AccordionContent className = "font-body" >
Content uses body font for readability.
</ AccordionContent >
</ AccordionItem >
</ Accordion >
Tabs
import { Tabs , TabsList , TabsTrigger , TabsContent } from "@/components/ui/tabs" ;
< Tabs defaultValue = "tab1" >
< TabsList >
< TabsTrigger value = "tab1" className = "font-heading" > Features </ TabsTrigger >
< TabsTrigger value = "tab2" className = "font-heading" > Pricing </ TabsTrigger >
</ TabsList >
< TabsContent value = "tab1" className = "font-body" >
Feature description text...
</ TabsContent >
</ Tabs >
Dialog
import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription } from "@/components/ui/dialog" ;
< Dialog >
< DialogContent >
< DialogHeader >
< DialogTitle className = "font-heading" > Dialog Title </ DialogTitle >
< DialogDescription className = "font-body" >
This description uses the body font.
</ DialogDescription >
</ DialogHeader >
</ DialogContent >
</ Dialog >
Badge
import { Badge } from "@/components/ui/badge" ;
{ /* Default: uses body font */ }
< Badge > New </ Badge >
{ /* Monospace for technical badges */ }
< Badge className = "font-mono" > v2.0.1 </ Badge >
{ /* Heading font for emphasis */ }
< Badge className = "font-heading font-bold" > Featured </ Badge >
Select
import { Select , SelectTrigger , SelectContent , SelectItem , SelectValue } from "@/components/ui/select" ;
< Select >
< SelectTrigger className = "font-body" >
< SelectValue placeholder = "Choose option" />
</ SelectTrigger >
< SelectContent >
< SelectItem value = "option1" className = "font-body" > Option 1 </ SelectItem >
< SelectItem value = "option2" className = "font-body" > Option 2 </ SelectItem >
</ SelectContent >
</ Select >
Dark Mode Considerations
Fonttrio pairings work seamlessly with shadcn/ui’s dark mode:
import { ThemeProvider } from "@/components/theme-provider" ;
import { editorial } from "@/registry/pairings/editorial" ;
export default function RootLayout ({ children }) {
return (
< html
className = { `
${ editorial . heading . variable }
${ editorial . body . variable }
${ editorial . mono . variable }
` }
suppressHydrationWarning
>
< body >
< ThemeProvider attribute = "class" defaultTheme = "system" >
{ children }
</ ThemeProvider >
</ body >
</ html >
);
}
Adjusting Font Weight for Dark Mode
Some fonts may need lighter weights in dark mode:
@layer base {
h1 {
font-family : var ( --font-heading );
font-weight : 700 ;
}
.dark h1 {
font-weight : 600 ; /* Lighter in dark mode */
}
.dark body , .dark p {
font-weight : 400 ; /* Ensure readability */
}
}
Best Practices
1. Maintain Consistency
Use the same fonts for similar UI elements:
// Good: Consistent button text
< Button > Submit </ Button >
< Button > Cancel </ Button >
// Avoid: Mixing fonts arbitrarily
< Button className = "font-heading" > Submit </ Button >
< Button className = "font-mono" > Cancel </ Button >
2. Respect Component Semantics
Use heading fonts for titles, body fonts for content:
// Good
< CardTitle className = "font-heading" > Title </ CardTitle >
< CardDescription className = "font-body" > Description </ CardDescription >
// Avoid
< CardTitle className = "font-mono" > Title </ CardTitle >
3. Test with Real Content
Test components with actual text, not placeholder content:
// Test with realistic lengths
< CardTitle > Understanding the Impact of Typography on User Experience </ CardTitle >
< CardDescription >
Typography plays a crucial role in how users perceive and interact with
your application. Choosing the right fonts can improve readability and
establish visual hierarchy.
</ CardDescription >
4. Consider Font Fallbacks
Ensure graceful degradation:
import { Inter } from "next/font/google" ;
const inter = Inter ({
subsets: [ "latin" ],
display: "swap" ,
fallback: [ "system-ui" , "arial" ], // Fallback fonts
});
5. Use Monospace Appropriately
Reserve monospace fonts for code and technical content:
// Good uses
< code className = "font-mono" > npm install </ code >
< Badge className = "font-mono" > v1.0.0 </ Badge >
< Input className = "font-mono" type = "text" placeholder = "API Key" />
// Avoid
< Button className = "font-mono" > Click me </ Button >
< CardTitle className = "font-mono" > Welcome </ CardTitle >
Building a Design System
Create a comprehensive design system with Fonttrio + shadcn/ui:
Define Typography Tokens
export const typography = {
fonts: {
heading: "var(--font-heading)" ,
body: "var(--font-body)" ,
mono: "var(--font-mono)" ,
},
sizes: {
xs: "0.75rem" ,
sm: "0.875rem" ,
base: "1rem" ,
lg: "1.125rem" ,
xl: "1.25rem" ,
"2xl" : "1.5rem" ,
"3xl" : "1.875rem" ,
"4xl" : "2.25rem" ,
},
weights: {
light: 300 ,
normal: 400 ,
medium: 500 ,
semibold: 600 ,
bold: 700 ,
},
lineHeights: {
tight: "1.2" ,
normal: "1.5" ,
relaxed: "1.65" ,
loose: "1.75" ,
},
};
Create Reusable Typography Components
components/typography/heading.tsx
import { cn } from "@/lib/utils" ;
import { typography } from "@/lib/design-tokens" ;
interface HeadingProps extends React . HTMLAttributes < HTMLHeadingElement > {
level ?: 1 | 2 | 3 | 4 | 5 | 6 ;
as ?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" ;
}
export function Heading ({
level = 1 ,
as ,
className ,
children ,
... props
} : HeadingProps ) {
const Tag = as || `h ${ level } ` as const ;
return (
< Tag
className = { cn (
"font-heading scroll-m-20" ,
className
) }
{ ... props }
>
{ children }
</ Tag >
);
}
components/typography/text.tsx
import { cn } from "@/lib/utils" ;
interface TextProps extends React . HTMLAttributes < HTMLParagraphElement > {
size ?: "sm" | "base" | "lg" ;
variant ?: "body" | "mono" ;
}
export function Text ({
size = "base" ,
variant = "body" ,
className ,
children ,
... props
} : TextProps ) {
const fontClass = variant === "mono" ? "font-mono" : "font-body" ;
return (
< p
className = { cn (
fontClass ,
size === "sm" && "text-sm" ,
size === "base" && "text-base" ,
size === "lg" && "text-lg" ,
className
) }
{ ... props }
>
{ children }
</ p >
);
}
Use in components:
import { Heading } from "@/components/typography/heading" ;
import { Text } from "@/components/typography/text" ;
< div >
< Heading level = { 1 } > Page Title </ Heading >
< Text size = "lg" > Introduction paragraph... </ Text >
< Heading level = { 2 } > Section Title </ Heading >
< Text > Section content... </ Text >
< Text variant = "mono" size = "sm" > Technical note </ Text >
</ div >
Troubleshooting
Fonts Not Applying to Components
If shadcn/ui components don’t show your fonts:
Verify font variables are in the <html> element, not <body>
Check that globals.css is imported before component usage
Inspect in DevTools to see which font is actually applied
Ensure Tailwind isn’t purging the font classes (check tailwind.config.js)
Component Styles Overriding Fonts
If component styles override your fonts:
Use !important sparingly: className="!font-heading"
Increase specificity by editing the component file directly
Check the component’s default className prop for conflicting font settings
Fonts Look Different Than Preview Site
If your implementation doesn’t match the Fonttrio preview:
Verify you’re using the same typography scale (check globals.css)
Ensure font weights are loading correctly (check Network tab)
Compare line height and letter spacing values
Check that you’re not overriding font settings with Tailwind classes
Example: Complete Component Integration
Here’s a full example of a Card component with proper typography:
components/feature-card.tsx
import { Card , CardHeader , CardTitle , CardDescription , CardContent } from "@/components/ui/card" ;
import { Badge } from "@/components/ui/badge" ;
import { Button } from "@/components/ui/button" ;
interface FeatureCardProps {
title : string ;
description : string ;
version : string ;
codeExample : string ;
}
export function FeatureCard ({
title ,
description ,
version ,
codeExample
} : FeatureCardProps ) {
return (
< Card >
< CardHeader >
< div className = "flex items-start justify-between" >
< CardTitle className = "font-heading text-2xl" >
{ title }
</ CardTitle >
< Badge className = "font-mono text-xs" >
{ version }
</ Badge >
</ div >
< CardDescription className = "font-body text-base leading-relaxed" >
{ description }
</ CardDescription >
</ CardHeader >
< CardContent >
< pre className = "font-mono text-sm bg-muted p-4 rounded-md overflow-x-auto" >
< code > { codeExample } </ code >
</ pre >
< Button className = "font-body mt-4 w-full" >
Learn More
</ Button >
</ CardContent >
</ Card >
);
}
Usage:
< FeatureCard
title = "Typography Scale"
description = "Each pairing includes a complete typography scale from h1 through body text, with carefully tuned sizes, weights, and spacing."
version = "v2.0"
codeExample = { `const scale = {
h1: { size: "2.25rem", weight: 700 },
body: { size: "1rem", lineHeight: "1.65" },
};` }
/>
Next Steps
Browse Pairings Explore all 49 curated font pairings with live previews.
Customize Typography Fine-tune font sizes, weights, and spacing for your design.