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:
Editorial Pairing
Minimal Pairing
{
"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:
@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.25 rem ;
--font-size-h2 : 1.875 rem ;
--font-size-h3 : 1.5 rem ;
--font-size-body : 1 rem ;
/* Line heights */
--line-height-heading : 1.2 ;
--line-height-body : 1.65 ;
/* Letter spacing */
--letter-spacing-tight : -0.025 em ;
--letter-spacing-normal : 0 em ;
}
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:
: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:
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:
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 : 2 rem ;
}
@media ( min-width : 768 px ) {
:root {
--font-size-h1 : 2.5 rem ;
}
}
h1 {
font-size : var ( --font-size-h1 );
}
Pattern 3: User Preferences
/* Default */
:root {
--font-size-base : 1 rem ;
}
/* User selected large text */
body .text-large {
--font-size-base : 1.25 rem ;
}
/* 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