Font Pairing Generator

Fourteen font pairings that work, set in your own words, with the CSS, the theme.json or the link tags to put them on a site.

Enable JavaScript to try the other pairings and to see the faces themselves; without it the preview shows the fallback stack.

Heading face first, body face second. Every one is on Google Fonts.

Your own words, because a pairing that looks right in Latin filler can fall apart in a real headline.

The size before the correction. A face with a small x-height is set larger than this, and one with a large x-height smaller.

h1. h2 and h3 follow at 68 and 50 percent of it.

Output

The CSS carries an @import. For a production site use the link tags instead: an @import blocks until it resolves.

Editorial

Ship the thing that reads well

A paragraph long enough to judge the body face by, which means more than one line of it. What you are looking for is whether the words hold together as shapes at this size, and whether the heading above looks like it belongs to the same page rather than to a different one.

17px / 1.6 · h1 2.5rem · asked for 17px

Editorial. The magazine default. High contrast strokes in the heading, a neutral workhorse underneath.

Playfair Display 700
ABCDEFGHIJKLM abcdefghijklm 0123456789
Source Sans 3 400
ABCDEFGHIJKLM abcdefghijklm 0123456789
fonts.css
/* Playfair Display + Source Sans 3 — Editorial */
@import url("https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Source+Sans+3:wght@400;600&display=swap");

:root {
	--font-heading: "Playfair Display", Georgia, "Times New Roman", serif;
	--font-body: "Source Sans 3", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

body {
	font-family: var(--font-body);
	font-size: 17px;
	font-weight: 400;
	line-height: 1.6;
}

h1,
h2,
h3,
h4,
h5,
h6 {
	font-family: var(--font-heading);
	font-weight: 700;
	line-height: 1.15;
	letter-spacing: -0.01em;
}

h1 {
	font-size: 2.5rem;
}

h2 {
	font-size: 1.7rem;
}

h3 {
	font-size: 1.25rem;
}

Most font pairing tools let you cross twenty heading faces with twenty body faces, which mostly produces four hundred bad pairs. This one has fourteen, each picked for a job, each with the weights it needs and the size correction that keeps the body text from looking two sizes smaller than it is.

Set your own headline in it. A pairing that looks right in Latin filler can fall apart in a real one.

How to use

  1. Pick a pairing. The name is the heading face first, the body face second, and the line under the preview says what it is for and what to watch out for.
  2. Put your own headline in Heading text. Real words, with your real capitalisation.
  3. Set the body size and the heading size. The heading size is h1; h2 and h3 follow at 68 and 50 percent of it, which is close enough to a 1.5 scale to be predictable.
  4. Take the CSS, or switch to theme.json for a block theme, or to the link tags if you are putting the fonts in a <head> yourself.

The preview loads the two families from Google Fonts once the page is interactive. Until then it shows the fallback stack, which is what your visitors see while the fonts are still in flight.

Example

Fraunces + Inter, at a 17px body:

/* Fraunces + Inter — Editorial */
@import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@700&family=Inter:wght@400;600&display=swap");

:root {
	--font-heading: "Fraunces", Georgia, "Times New Roman", serif;
	--font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

body {
	font-family: var(--font-body);
	font-size: 16.5px;
	font-weight: 400;
	line-height: 1.6;
}

Two things in there are not the obvious choice.

The body size is 16.5px, not 17. Inter has a large x-height, so it reads a little larger than its nominal size; setting it slightly smaller makes it match the 17px you asked for. Libre Baskerville goes the other way, to 17.5px.

The heading gets letter-spacing: -0.015em. Display faces are drawn at large sizes and spaced for them, so at 40px they still look loose. The correction is per pairing, because Cormorant Garamond wants none at all.

Pitfalls

The @import in the CSS blocks rendering. It is there because a single snippet that works when pasted into a stylesheet is more useful than one that does not. For a site you care about, use the link tags output and put them in <head> with the preconnects, or enqueue the fonts properly in WordPress.

display=swap is in the URL for a reason. Without it, the browser hides text for up to three seconds waiting for the font. With it, the fallback shows immediately and is replaced. That is the swap you see on a slow connection, and it is the better of the two failures.

Only the weights in the snippet are loaded. Ask for font-weight: 500 on something and you will get a synthesised weight rather than the real one. Add the weight to the URL if you need it.

A monospace heading needs to be short. JetBrains Mono over six words stops reading as a headline and starts reading as a filename.

Fonts loaded from Google are a third-party request. In the EU that is a consideration for a public site. Downloading the files and self-hosting them, or using the WordPress font library, avoids it entirely; the CSS here works either way once the @import is replaced with your own @font-face rules.

Compatibility

The CSS is plain custom properties and element selectors, so it works in any theme, classic or block. The theme.json output is version 3, which WordPress 6.6 and later read; it declares the two families under settings.typography.fontFamilies so they appear in the editor’s font picker, and it turns fluid typography on.

theme.json on its own does not load the files. Either keep the link tags in <head>, enqueue the Google URL, or add the families through the WordPress font library, which downloads and self-hosts them.

Every family here is an open licence face on Google Fonts, so self-hosting is allowed.

Frequently asked questions

Why fourteen pairings and not a font picker?
Because the interesting part of a pairing is not which two names are in it, it is whether the two faces have compatible proportions at the sizes you will actually use. That is a judgement, so it is made here once per pair rather than left to a grid of combinations.
Can I use one of these for both heading and body?
Yes, and a few are meant to be: Lora holds up at body sizes, and Archivo Black with Archivo is one family at two extremes. Set --font-heading and --font-body to the same stack.
Does the preview show what my theme will show?
The faces and their sizes, yes. The spacing around them is this page’s, not your theme’s, so a heading that sits tight here may sit loose in a post.
Why is my body text not exactly the size I typed?
The size correction. The number you type is the size you want it to read as; the number in the output is what gets it there for that particular face. The line under the preview says which way it was moved and why.
How do I self-host these instead?
Download the family from Google Fonts, put the woff2 files in the theme, and replace the @import with @font-face rules pointing at them. Keep the same --font-heading and --font-body values and nothing else changes.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.