Guide
Font pairing for websites: how to combine fonts without a designer
How to pair fonts for a website: how many to use, serif and sans serif, x-height, roles, eight proven free Google Font pairings, CSS and Tailwind code, and a prompt for your agent.
Most sites generated with an AI tool ship with a single font, usually whatever the starter template carried. That works, but it also reads like every other site out there. A second font reserved for headlines is the quickest route to giving a page its own voice, and equally the quickest route to visual mess when the two fight each other. This guide walks through choosing a pair that clicks, with eight pairings built from free fonts plus code for CSS, Tailwind and Next.js.
Step 1: Decide how many fonts you need
Begin with a single question: does the second font accomplish something the first cannot? The pairing guide from Google Fonts poses exactly that and recommends first exploring the weights, widths, sizes and letter spacing of your main font. One solid family carrying a light, a regular and a bold weight can hold up a whole site on its own.
A second font justifies itself when headlines need a different tone, when your main font has no italic or heavy weight, or when you show code. A workable ceiling:
- One font: product UIs, dashboards, documentation.
- Two fonts: landing pages, marketing sites, blogs. Treat this as the default.
- Three fonts: the two fonts above plus a monospace for code, for developer products.
Each additional font means one more file your visitors have to download. The Next.js docs put it bluntly: use multiple fonts conservatively.
Step 2: Give every font a role
The moment you think in jobs rather than fonts, pairing turns simple. A website has four:
- Display: the big headline and the section titles. Just a few words at large sizes. Personality lives here.
- Text: paragraphs, the words people actually read. It needs to stay comfortable at 16 to 18 pixels.
- UI: buttons, navigation, form labels, small print. Most of the time this is the same font as text.
- Code: snippets, API keys, terminal commands. This one is always a monospace font.
Most strong pairings boil down to one display font and one font covering both text and UI. Fonts drawn for large sizes tend to read badly inside paragraphs. The Google Fonts entry for Playfair Display even labels it a display design and points you toward a different font for body text.
Step 3: Pick contrast, not near-twins
The truly difficult part of pairing is locating a second font that differs enough yet not too much. Jason Santa Maria, cited in the Google Fonts pairing guide, offers the rule of thumb: when in doubt, pair a serif and a sans serif.
What goes wrong is the reverse: two fonts that are nearly identical, like two neutral sans serifs. Visitors cannot name the difference, they just sense that something is wrong. Should your two fonts be mistakable for one another, cut one.
Contrast does not have to mean serif against sans:
- Structure: serif headlines over a sans body, or flipped.
- Width: a condensed display font above a text font of normal width.
- Weight: a heavy headline resting on a light, open text font.
- Personality: a quirky grotesk next to a neutral workhorse.
A single strong contrast does the job. Put two expressive fonts on one page and they compete for the eye.
Step 4: Match x-height and proportions
The x-height describes how tall lowercase letters like x, a and e are. It explains why two fonts both set at 16 pixels can read as two different sizes.
We measured several popular fonts from the files Google Fonts distributes. Their x-height expressed as a share of the font size:
- Inter: 0.546
- Source Serif 4: 0.475
- IBM Plex Sans, IBM Plex Serif and IBM Plex Mono: 0.516 each
- Geist and Geist Mono: 0.530 each
- Instrument Sans and Instrument Serif: 0.510 each
At 16 pixels, Inter's lowercase letters stand about 8.7 pixels tall while Source Serif 4's come to about 7.6. Side by side, the serif reads a size smaller. In headlines this hardly matters. Where fonts meet inside a single line it does: think inline code within a paragraph, or a serif word dropped into sans text.
Here lies the quiet strength of a superfamily, meaning a set of serif, sans and frequently mono designs built on one shared skeleton. The IBM Plex figures above are identical, so those fonts sit together with no adjustments.
Where fonts mismatch, CSS can reconcile them. font-size-adjust rescales a font until its x-height hits a ratio you specify, and every current major browser has supported it since July 2024:
/* Inter body text, Source Code Pro inline code */
code {
font-family: "Source Code Pro", ui-monospace, monospace;
font-size-adjust: 0.546; /* Inter's x-height ratio */
}
Step 5: Match the mood to your brand
People respond to type emotionally before they read a single word. Put that to work on purpose:
- Warm, friendly, human: a soft serif or a rounded sans for headlines.
- Premium, editorial, classic: a high-contrast serif with fine hairlines for headlines.
- Technical, precise, trustworthy: a clean sans paired with a matching monospace.
- Calm, made for reading: a serif built for body text.
Pick the headline font for its mood and the text font for its readability, then try both against a real headline and paragraph taken from your own site.
Eight pairings from free fonts
Each of these lives on Google Fonts under the SIL Open Font License, which permits commercial websites.
- Fraunces + Inter (warm, editorial). Fraunces is a soft, old-style display serif offering variable axes for weight, optical size and softness. Inter was made for screens and carries a tall x-height.
- Playfair Display + Source Sans 3 (classic, elegant). Playfair brings high contrast and fine hairlines. Source Sans 3, Adobe's first open-source family, keeps the body text quiet.
- DM Serif Display + DM Sans (bold, confident). Both are by Colophon Foundry. DM Serif Display is shaped for very large sizes and ships in one weight, regular plus italic.
- Instrument Serif + Instrument Sans (contemporary, magazine). Same designers, identical x-height. Instrument Serif is a condensed display serif, also restricted to regular and italic only.
- Source Serif 4 + Source Sans 3 + Source Code Pro (docs, long reading). Source Serif 4 was designed to accompany Source Sans, with proportions matched between them. The mono covers code.
- IBM Plex Serif + IBM Plex Sans + IBM Plex Mono (technical, sober). A genuine superfamily with shared proportions and every role covered.
- Geist + Geist Mono (developer tools). From Vercel, designed as a pair. If
create-next-appwas your starting point, you have them already, which is also why so many new sites look alike. - Space Grotesk + Inter (techy with character). Space Grotesk grows out of the monospace Space Mono and keeps its odd details. Run it large, and leave the reading to Inter.
Put the pairing into code
Declare the roles once as CSS variables and reference only those variables from then on. Load the font files beforehand, ideally hosted on your own server (how to self-host web fonts).
:root {
--font-display: "Fraunces", Georgia, serif;
--font-text: "Inter", system-ui, sans-serif;
--font-code: "JetBrains Mono", ui-monospace, monospace;
}
body { font-family: var(--font-text); }
h1, h2, h3 { font-family: var(--font-display); }
code, pre { font-family: var(--font-code); }
In Next.js and Tailwind
next/font/google fetches the files at build time and delivers them from your own domain. Assign a CSS variable to each font:
// app/layout.tsx
import { Fraunces, Inter, JetBrains_Mono } from "next/font/google";
const fraunces = Fraunces({ subsets: ["latin"], variable: "--font-fraunces" });
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains-mono" });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${fraunces.variable} ${inter.variable} ${mono.variable}`}>
<body className="font-sans">{children}</body>
</html>
);
}
Next, map those variables onto roles inside your Tailwind v4 CSS:
@import "tailwindcss";
@theme inline {
--font-display: var(--font-fraunces);
--font-sans: var(--font-inter);
--font-mono: var(--font-jetbrains-mono);
}
From here font-display, font-sans and font-mono work as utilities: <h1 class="font-display text-5xl">. Under Tailwind v3, set the same variables in theme.extend.fontFamily within tailwind.config.js.
Tell your agent which pairing to use
Without a system from you, agents choose fonts inconsistently. Spell out the roles along with what is off limits:
Prompt for your agent
Use a fixed font system for this site: Fraunces for h1 to h3 only, Inter for body text, buttons, navigation and forms, JetBrains Mono only for code. Load all three with next/font/google as CSS variables, map them to font-display, font-sans and font-mono in the Tailwind theme, remove every other font-family declaration, and do not add any other font. List the files you changed.
Copy the same lines into your project instructions (CLAUDE.md, AGENTS.md or your tool's rules) so that new pages stick to them.
Common mistakes
- Two fonts that are almost the same. Two neutral sans serifs together look like an accident. Choose one.
- A display font for paragraphs. Fonts drawn for large sizes wear the eye out in body text.
- Faux bold. Browsers make headings bold by default, and plenty of templates stack bold classes on top. If a display font ships with only a regular weight, like DM Serif Display or Instrument Serif, the browser invents the bold and it ends up smudgy. Give those headings
font-weight: 400, or addfont-synthesis: none. - Too many weights. Every static weight adds a file. Reach for a variable font or limit yourself to two or three weights.
- A leftover rule that wins. The
create-next-apptemplate putsfont-family: Arial, Helvetica, sans-serifonbodyinsideglobals.css. If your text refuses to leave Arial, that is the place to look. - Missing characters. Confirm that both fonts cover your languages, accents and umlauts included.
Questions people ask
How many fonts should a website use? Two covers most cases: one for headlines, one for text and UI, with a monospace added when you show code.
Can I pair two sans serif fonts? You can, provided they differ clearly in character or width, like a quirky grotesk for headlines and a neutral sans for text. Two similar neutral sans serifs do not work.
What is the best Google Fonts pairing? No single best one exists; it should suit your brand's mood and survive the checks above. A superfamily like IBM Plex is the safest first step.
Are Google Fonts free for commercial websites? Nearly all carry the SIL Open Font License or the Apache License, and each allows commercial use. Under the OFL, selling the font files by themselves is not allowed. Verify the license on each font's Google Fonts page.
Should the headline font or the text font come first? Pick the text font first, since it shoulders most of the reading. After that, look for a headline font that contrasts with it.
A headline font nobody else has
The pairings in this guide do work, and that is precisely why so many other sites run them as well. The text font can remain a proven workhorse. The headline font is where a site finds its voice, and that is the piece MakeFont is being built for: your agent will design a display typeface for your project, one that sits well beside the text font you already use. MakeFont is in development, and early access is open. Read more in What is MakeFont.