Skip to main content
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:
1

Install shadcn/ui

Set up shadcn/ui in your Next.js project.
npx shadcn@latest init
2

Install a Fonttrio Pairing

Add your chosen font pairing.
npx shadcn@latest add https://www.fonttrio.xyz/r/editorial.json
3

Apply Fonts to Layout

Add font variables to your root layout.
app/layout.tsx
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>
  );
}

Button Components

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>

Form Components

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:
components/ui/card.tsx
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:
components/ui/button.tsx
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:
app/globals.css
@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:
tailwind.config.js
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:
app/globals.css
@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

lib/design-tokens.ts
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:
  1. Verify font variables are in the <html> element, not <body>
  2. Check that globals.css is imported before component usage
  3. Inspect in DevTools to see which font is actually applied
  4. Ensure Tailwind isn’t purging the font classes (check tailwind.config.js)

Component Styles Overriding Fonts

If component styles override your fonts:
  1. Use !important sparingly: className="!font-heading"
  2. Increase specificity by editing the component file directly
  3. 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:
  1. Verify you’re using the same typography scale (check globals.css)
  2. Ensure font weights are loading correctly (check Network tab)
  3. Compare line height and letter spacing values
  4. 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.

Build docs developers (and LLMs) love