Chapter 04 — Web Typography

Pairing Typefaces

Chapter 03 closed the arrangement work: scale, measure, leading. None of it touched the typefaces themselves. This chapter does.

Search for typeface pairing advice and you get the same two phrases on every result. “Pick fonts with different personalities.” “Contrast without conflict.” Both sound like guidance. Neither tells you what to do. They describe the destination and skip the route. Most pairing resources work the same way: they show you a gallery of pairs that work and trust you to absorb the pattern by looking. That’s not a method. It’s a mood board.

The reason a pair works or fails is mechanical, and the mechanics are learnable. That’s what this chapter is about.


First, do you even need a second typeface?

Before any pairing advice, the honest question: a lot of projects don’t need a second typeface at all.

A single well-chosen family with a real range of weights can carry an entire site. Headings in the bold or black weight, body in regular, captions and metadata in the lighter weights or in small caps. The hierarchy comes from size and weight, both of which Chapter 01 and Chapter 03 already gave you a system for. Plenty of sites that look like they have considered typography are running one family. You just can’t tell, because nothing about good single-typeface work announces itself.

A second typeface adds a second font file to load, a second set of metrics to reconcile, and a new way for the page to look slightly off. None of that is a reason to avoid it. It is a reason to be sure the project wants it before you commit. If one family does the job, let it.

When you do want a pair, here is what you are actually looking for.


Contrast

The two typefaces have to read as clearly different at a glance. Too similar and the pair looks like a mistake, like one font failed to load and a fallback crept in. Too different and they fight for the reader’s attention instead of dividing the work between them.

The most reliable way to get clean contrast is serif plus sans-serif. The difference there is categorical, not a matter of degree. A reader doesn’t have to notice it consciously to register that the heading and the body are doing different jobs. Two serifs or two sans-serifs can be paired well, but it takes more judgment, because now the contrast lives in subtler features and the failure mode (looks like a mistake) is closer at hand.


Shared proportions

Strong contrast is necessary. It isn’t sufficient. A pair holds together better when the two typefaces share proportions underneath the contrast, and the proportion that matters most is x-height: the height of the lowercase letters relative to the capitals.

Two typefaces with very different x-heights look mismatched even when each one is beautiful on its own. Set a tall-x-height sans next to a low-x-height serif and the body text reads larger than the headings imply it should, the optical sizes pull apart, and the page feels assembled from parts that were never meant to meet. Check the lowercase x of both faces at the same point size. If one is noticeably taller, the pair will need correcting or replacing.

Stroke contrast is the next thing to check. It describes how much a typeface varies between its thick and thin strokes. A high-contrast serif like Didot has dramatic thick-to-thin variation. Pair it with a high-contrast sans like Optima, which carries similar modulation, and the two feel related. Pair that same Didot with a geometric sans whose strokes are a uniform width, and you get tension: one face is full of modulation, the other has none, and the eye reads the mismatch even if the reader can’t name it.


Weight compatibility

Weight names lie. One typeface’s “Bold” can be visibly heavier than another’s. When the serif runs the headings and the sans runs the body, the heading weight needs to feel optically equal to the body weight, balanced, not one stem looking starved next to the other.

You cannot get this from the weight name in the CSS. A 700 in one family is not a 700 in another. Set them next to each other on screen and look. If the heading feels thin against the body, step it up a weight. If it feels heavy and shouty, step it down. The number is a label, not a measurement.


Variable fonts collapse the problem

One case sidesteps most of the above. A variable font family can contain both serif and sans-serif subfamilies in a single file. Recursive is the clearest example: one download gives you a sans and a slab serif tuned to share metrics and proportion. The pairing decisions are already made, by the people who drew the faces, and you get the contrast from one file instead of two.

This isn’t the default situation, and it shouldn’t change how you think about pairing in general. But when a project’s needs line up with a family built this way, it removes a real source of trouble.


The CSS

The implementation is small. Declare both families as custom properties next to the tokens from the earlier chapters, then apply the sans to the body and the serif to the headings:

css~/tutorials/
:root {
  --font-serif: 'Fraunces', Georgia, serif;
  --font-sans: 'DM Sans', system-ui, sans-serif;
}

body {
  font-family: var(--font-sans);
}

h1, h2, h3, h4 {
  font-family: var(--font-serif);
}

Each custom property carries its own fallback stack, so a generic family always sits at the end. That fallback is doing more than it looks like, which is the subject of the next chapter.


Where to look, and what to avoid

Start with Fonts In Use (fontsinuse.com), a curated archive of real-world typeface usage: search a typeface and you see what working designers have set it alongside. Typewolf (typewolf.com) catalogs site inspiration and font recommendations, useful for seeing how a face behaves in production rather than on a specimen page. Google Fonts has a “Pairings” tab on each font’s page that suggests combinations other designers have used together. Not authoritative, but a fair starting point.

A few pairs to walk away from. Two serifs or two sans-serifs that are visually close: this is the mistake-looking failure, and it’s the easiest one to fall into. Pairing two typefaces because you recognize both names: reputation says nothing about whether two specific faces share x-height and stroke contrast.


What comes next

You’ve now added a second font to the project. That’s a second file the browser has to fetch before the page can settle, and fonts have a habit of arriving late and shifting the layout when they do. Chapter 05 covers loading web fonts without paying for them in performance: font-display, preloading, and keeping the page stable while the real typefaces are still on their way.