Skip to main content

CSS Variables

Fonttrio uses CSS custom properties (CSS variables) to provide a flexible, maintainable system for applying font pairings to your project. This approach makes it easy to swap fonts, customize scales, and maintain consistency across your entire application.

Why CSS Variables?

CSS variables offer several advantages for managing typography:
  • Single source of truth: Define fonts once, use everywhere
  • Easy theming: Switch entire pairings by changing a few variables
  • Dynamic updates: Change typography at runtime without recompiling
  • Scoped customization: Override variables for specific components
  • Framework agnostic: Works with any CSS-based framework

Core Font Variables

Every Fonttrio pairing defines three core CSS variables for the font families:
:root {
  --font-heading: var(--font-playfair-display);
  --font-body: var(--font-source-serif-4);
  --font-mono: var(--font-jetbrains-mono);
}
These three variables are then referenced throughout your stylesheets to apply the fonts consistently.

Variable Structure in Pairings

Each pairing in the Fonttrio registry defines its CSS variables in the cssVars section:
{
  "name": "pairing-editorial",
  "cssVars": {
    "theme": {
      "--font-heading": "var(--font-playfair-display)",
      "--font-body": "var(--font-source-serif-4)",
      "--font-mono": "var(--font-jetbrains-mono)"
    }
  },
  "css": {
    "h1": { 
      "font-family": "var(--font-heading)", 
      "font-size": "2.25rem", 
      "line-height": "1.2", 
      "letter-spacing": "-0.025em", 
      "font-weight": "700" 
    },
    "h2": { 
      "font-family": "var(--font-heading)", 
      "font-size": "1.875rem", 
      "line-height": "1.25", 
      "letter-spacing": "-0.02em", 
      "font-weight": "600" 
    },
    "h3": { 
      "font-family": "var(--font-heading)", 
      "font-size": "1.5rem", 
      "line-height": "1.3", 
      "letter-spacing": "-0.015em", 
      "font-weight": "600" 
    },
    "h4, h5, h6": { 
      "font-family": "var(--font-heading)", 
      "letter-spacing": "-0.01em" 
    },
    "body, p": { 
      "font-family": "var(--font-body)", 
      "line-height": "1.65" 
    },
    "code, pre": { 
      "font-family": "var(--font-mono)" 
    }
  }
}

How Variables are Applied

Fonttrio uses a two-layer variable system for maximum flexibility:

Layer 1: Font Family Variables

Individual fonts define their own CSS variable:
/* Each font gets its own variable */
--font-playfair-display: 'Playfair Display', serif;
--font-source-serif-4: 'Source Serif 4', serif;
--font-jetbrains-mono: 'JetBrains Mono', monospace;

Layer 2: Semantic Variables

Pairings reference these font variables through semantic names:
/* Pairing maps semantic names to specific fonts */
--font-heading: var(--font-playfair-display);
--font-body: var(--font-source-serif-4);
--font-mono: var(--font-jetbrains-mono);
This two-layer approach means you can:
  • Switch pairings by changing only 3 variables
  • Override specific fonts while keeping others
  • Create variations of existing pairings

Using Variables in Your CSS

Once a pairing is installed, use the semantic variables in your stylesheets:
/* Headings use the heading font */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
}

/* Body text uses the body font */
body, p, li, td {
  font-family: var(--font-body);
}

/* Code uses the monospace font */
code, pre, kbd, samp {
  font-family: var(--font-mono);
}

Fonttrio’s Global CSS Setup

Here’s how Fonttrio sets up its own CSS variable system in /home/daytona/workspace/source/app/globals.css:85-98:
app/globals.css
@theme inline {
  --color-bg: var(--bg);
  --color-text: var(--text);
  --color-text-muted: var(--text-muted);
  --color-text-subtle: var(--text-subtle);
  --color-surface-border: var(--surface-border);
  --color-surface-border-strong: var(--surface-border-strong);
  --color-surface: var(--surface);
  --color-surface-hover: color-mix(in oklch, var(--surface) 80%, var(--foreground) 20%);

  --font-display: var(--font-bebas-neue), system-ui, sans-serif;
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}
This shows how Fonttrio extends CSS variables beyond just fonts to include colors and other design tokens.

Typography-Specific Variables

Beyond font families, you can create variables for the entire typography scale:
:root {
  /* Font families */
  --font-heading: var(--font-playfair-display);
  --font-body: var(--font-source-serif-4);
  --font-mono: var(--font-jetbrains-mono);
  
  /* Typography scale */
  --font-size-h1: 2.25rem;
  --font-size-h2: 1.875rem;
  --font-size-h3: 1.5rem;
  --font-size-body: 1rem;
  
  /* Line heights */
  --line-height-heading: 1.2;
  --line-height-body: 1.65;
  
  /* Letter spacing */
  --letter-spacing-tight: -0.025em;
  --letter-spacing-normal: 0em;
}

h1 {
  font-family: var(--font-heading);
  font-size: var(--font-size-h1);
  line-height: var(--line-height-heading);
  letter-spacing: var(--letter-spacing-tight);
}

Scoped Customization

CSS variables can be scoped to specific elements or components:
/* Global default */
:root {
  --font-heading: var(--font-inter);
  --font-body: var(--font-inter);
}

/* Override for marketing section */
.marketing-hero {
  --font-heading: var(--font-playfair-display);
}

/* Override for documentation */
.docs-content {
  --font-body: var(--font-source-serif-4);
  --line-height-body: 1.75; /* More readable for long-form */
}

/* Both h1 elements use var(--font-heading) but get different fonts */
h1 {
  font-family: var(--font-heading);
}
Scoped variables allow you to use different pairings in different parts of your site without conflicts.

Dark Mode Support

CSS variables make theme switching trivial. Here’s how Fonttrio handles dark mode in /home/daytona/workspace/source/app/globals.css:7-50:
app/globals.css
:root {
  --bg: #fafafa;
  --text: #0a0a0a;
  --text-muted: #666666;
  --surface-border: #eaeaea;
  /* ...more light mode colors */
}

.dark {
  --bg: #0a0a0a;
  --text: #ededed;
  --text-muted: #888888;
  --surface-border: #1f1f1f;
  /* ...more dark mode colors */
}
The variables automatically update when the .dark class is applied, without touching font definitions.

Dynamic Updates with JavaScript

You can change CSS variables programmatically:
// Switch to a different pairing at runtime
function switchPairing(pairing: 'editorial' | 'minimal' | 'brutalist') {
  const root = document.documentElement;
  
  if (pairing === 'editorial') {
    root.style.setProperty('--font-heading', 'var(--font-playfair-display)');
    root.style.setProperty('--font-body', 'var(--font-source-serif-4)');
    root.style.setProperty('--font-mono', 'var(--font-jetbrains-mono)');
  } else if (pairing === 'minimal') {
    root.style.setProperty('--font-heading', 'var(--font-geist)');
    root.style.setProperty('--font-body', 'var(--font-geist)');
    root.style.setProperty('--font-mono', 'var(--font-geist-mono)');
  }
  // Changes apply instantly across the entire site
}

// Adjust font size dynamically
function setFontSize(scale: number) {
  document.documentElement.style.setProperty('--font-size-body', `${scale}rem`);
}

Fallback Fonts

Always provide fallback fonts for better loading experience:
:root {
  /* Fonttrio fonts with system fallbacks */
  --font-heading: var(--font-playfair-display), Georgia, serif;
  --font-body: var(--font-source-serif-4), Georgia, serif;
  --font-mono: var(--font-jetbrains-mono), 'Courier New', monospace;
  
  /* Fonttrio uses system fallbacks */
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

Tailwind CSS Integration

If you’re using Tailwind, extend your config to use CSS variables:
tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontFamily: {
        heading: ['var(--font-heading)'],
        body: ['var(--font-body)'],
        mono: ['var(--font-mono)'],
      },
    },
  },
}
Then use in your HTML:
<h1 class="font-heading text-4xl">Heading</h1>
<p class="font-body text-base">Body text</p>
<code class="font-mono">Code</code>

Next.js Font Integration

Fonttrio works seamlessly with Next.js font optimization:
app/layout.tsx
import { Playfair_Display, Source_Serif_4, JetBrains_Mono } from 'next/font/google';

const playfair = Playfair_Display({
  subsets: ['latin'],
  variable: '--font-playfair-display',
  display: 'swap',
});

const sourceSerif = Source_Serif_4({
  subsets: ['latin'],
  variable: '--font-source-serif-4',
  display: 'swap',
});

const jetbrainsMono = JetBrains_Mono({
  subsets: ['latin'],
  variable: '--font-jetbrains-mono',
  display: 'swap',
});

export default function RootLayout({ children }) {
  return (
    <html className={`${playfair.variable} ${sourceSerif.variable} ${jetbrainsMono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

Variable Naming Conventions

Fonttrio follows these naming conventions:
/* ✅ Good: Semantic, purpose-driven names */
--font-heading
--font-body
--font-mono

/* ❌ Avoid: Font-specific names */
--font-playfair
--font-source

/* ✅ Good: Scale-specific variables */
--font-size-h1
--line-height-body
--letter-spacing-tight

/* ❌ Avoid: Non-descriptive names */
--size-1
--lh-a
--ls-small

Best Practices

Use Semantic Names: Prefer --font-heading over --font-playfair so you can swap fonts without changing references.
Layer Your Variables: Create base variables for individual fonts, then semantic variables that reference them.
Provide Fallbacks: Always include fallback fonts in case custom fonts fail to load.
Scope Strategically: Use scoped variables for component-level customization without affecting global styles.
Test Everywhere: CSS variables work in all modern browsers, but test your fallbacks in older environments.

Common Patterns

Pattern 1: Pairing Switcher

/* Define multiple pairings */
.pairing-editorial {
  --font-heading: var(--font-playfair-display);
  --font-body: var(--font-source-serif-4);
}

.pairing-minimal {
  --font-heading: var(--font-geist);
  --font-body: var(--font-geist);
}

/* Apply to body or root element */
body {
  font-family: var(--font-body);
}

Pattern 2: Responsive Typography

:root {
  --font-size-h1: 2rem;
}

@media (min-width: 768px) {
  :root {
    --font-size-h1: 2.5rem;
  }
}

h1 {
  font-size: var(--font-size-h1);
}

Pattern 3: User Preferences

/* Default */
:root {
  --font-size-base: 1rem;
}

/* User selected large text */
body.text-large {
  --font-size-base: 1.25rem;
}

/* All sizes scale proportionally */
body {
  font-size: var(--font-size-base);
}

Next Steps

Font Pairings

Learn about Fonttrio’s pairing structure

Typography Scale

Understand how typography scales work

Installation

Install Fonttrio and start using CSS variables

Customization

Customize pairings for your project

Build docs developers (and LLMs) love