Craft

Pairing a display serif with a geometric sans

A display serif and a geometric sans are an old and dependable pair: one carries voice and the other carries information. The pairing goes wrong in predictable ways, most of them to do with scale, not taste. What follows is a method for choosing and tuning the two, with a fictional specimen to apply it to. Numbers for sizes, tracking and line-height are starting points to judge by eye, not standards; the CSS behaviour described comes from MDN.

Give each face one job

Decide the roles before looking at specimens.

Display serifGeometric sans
JobHeadlines, large numerals, short quotes, one emphasised phraseBody text, navigation, buttons, labels, captions, tables
Sizes (rule of thumb)Roughly 28 px and upRoughly 12 to 24 px
WeightsOne or two: a regular and perhaps a light or mediumRegular, medium, perhaps semibold
CaseSentence case; italic for emphasisSentence case; small uppercase labels with added tracking
NeverLong paragraphs, small captionsLarge decorative headlines in thin weights

When both faces do both jobs the page loses its hierarchy. The simplest rule: if the text is meant to be read, it is sans; if it is meant to be seen, it is serif.

Contrast between them, agreement within them

A pair works when the faces differ clearly in one thing and agree in others. A serif and a sans already differ in structure, so make them agree in proportion and tone:

  • x-height. The height of lowercase letters relative to the font size. A sans with a tall x-height next to a serif with a short one looks larger even at the same pixel size. Match this before anything else.
  • Stroke contrast. A high-contrast serif has hairlines and heavy stems. A geometric sans is nearly monoline. Choose a serif weight whose thick stems are about as strong as the sans's regular stems, so neither face looks anaemic or heavy beside the other.
  • Width and openness. A wide geometric sans with circular bowls sits comfortably with a serif that has generous round shapes. A condensed serif beside a wide geometric sans feels mismatched.
  • Mood. Sharp, modern serif forms suit a precise sans. Soft, wedge-like or quirky serifs suit a friendlier sans.

You do not need numbers from a font tool to judge this. Set the same sentence in both faces at 24 px on one line, view it at 100 percent, and compare the lowercase.

Matching x-height with numbers

The font-size-adjust property scales whichever font is used so that a chosen metric becomes a fixed proportion of the font size. By default the metric is the x-height (ex-height), and the value is the aspect: x-height divided by font size. MDN states that the adjustment applies to each font used to render the text, not only to fallbacks, and that it is intended to keep a fallback's lowercase size consistent with the first-choice font. Support is described as Baseline 2024 (newly available), so it is an enhancement over a correct fallback.

Use it for what MDN says it is for: give each face its own measured aspect so that a fallback font is scaled to match the first-choice font. MDN adds that the number should generally match the aspect of the first-choice font, and that if you want to scale the first-choice font itself you should change font-size instead. Suppose the fictional display serif has an aspect of 0.45 (its x-height is 0.45 em) and the fictional sans has 0.52:

:root {
  --serif: "Specimen Serif", Georgia, serif;
  --sans:  "Specimen Sans", system-ui, sans-serif;
}
body     { font-family: var(--sans);  font-size-adjust: 0.52; }
.display { font-family: var(--serif); font-size-adjust: 0.45; }

Aligning the two faces with each other is a separate job, done with font-size. Where both appear in one line (a heading with an inline sans label, a button with a serif numeral), shrink the sans by 0.45 / 0.52, about 0.87, or enlarge the serif by 0.52 / 0.45, about 1.16, so the lowercase letters read at the same height. Read the real aspect values from the font's metrics or measure them in a specimen render; the numbers above are invented for the example.

Two cautions. Resizing a face changes how lines wrap, so re-check headline wrapping after you add a factor or a font-size-adjust value. And from-font, which takes the aspect from the first available font, is not a substitute for a measured number when the goal is to align a fallback with a web font, because the fallback has to be scaled to the web font's value, which you have to state. The fallback-metric approach in font loading without layout shift covers that side.

Optical size, tracking and line-height at display sizes

Type that works at 16 px is wrong at 96 px. Three things change as size goes up.

Optical size. Some variable fonts include an opsz axis that redraws the letters for the size they are shown at. According to MDN, optical sizing is on by default for fonts with that axis (font-optical-sizing: auto), small sizes often get thicker strokes and larger serifs, and large sizes get a more delicate drawing with more contrast between thick and thin strokes. You rarely need to touch it:

.display { font-optical-sizing: auto; }

If the family has no such axis, hairlines at 120 px can look thin and spacing loose. Choose a display cut of the family if one exists, or tighten the tracking by hand.

Tracking. Default letter-spacing is designed for text sizes. As letters grow, the gaps look wider, so display type usually wants negative tracking. Specify it in em so it scales with the size: start around -0.01em to -0.03em for large headlines and judge by eye on the word with the most irregular shape. Small uppercase labels often read better with positive tracking, roughly 0.06em to 0.14em. Never track lowercase body text. MDN warns that very large positive or negative values make words unreadable, and WCAG 1.4.12 (Text Spacing) expects content to survive a user setting letter spacing to at least 0.12 times the font size, so give label containers room to grow.

Line-height. MDN recommends a unitless line-height of at least 1.5 for main paragraph content. Headlines set over more than one line want much less, around 1.0 to 1.15 at large sizes, so the lines read as one shape. Check the descenders of one line against the ascenders of the next, especially in italic.

h1, .display {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.5rem, 7vw, 6rem);
  line-height: 1.04;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.label {
  font-family: var(--sans);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

text-wrap: balance evens out the line lengths of short blocks such as headings. MDN notes that browsers limit it to a small number of lines because balancing is computationally expensive, and it is Baseline 2024, so the layout must hold without it.

Italic as emphasis

A serif italic is one of the best devices in typography: a single phrase in italic inside an upright headline adds voice without adding a size or a colour.

  • Use it for one phrase per heading, usually the second half. Two italic words are emphasis; a whole italic line is a style change.
  • Load a real italic. If the family has no italic file, the browser may fabricate a slant from the roman, which distorts curves and spacing. font-synthesis: none turns synthesis off for weight, style, small caps and position together, so use font-synthesis-style: none if you only want to stop the fake italic and keep the rest.
  • Do not italicise the sans for emphasis in body text. Use weight, or leave emphasis to the serif in headings.
  • Check contrast. Italic display type in a dim colour loses its thin strokes first; see dark interfaces that stay readable.

A fictional specimen walk-through

Take a fictional quarterly, Northlight Review, with a display serif and a geometric sans. The page needs a masthead, a cover headline, a standfirst, a section label and body text.

  1. Cover headline: serif, clamp(2.75rem, 8vw, 6.5rem), line-height 1.02, tracking -0.025em. Text: "The harbour that refused to move". The italic is the one emphasised word.
  2. Standfirst: sans, 1.25 rem, line-height 1.5, a lighter text colour than the body. It explains the headline in one sentence so the serif can stay poetic.
  3. Section label: sans, 0.75 rem, uppercase, tracking 0.12em, medium weight, sitting above the headline.
  4. Body: sans, 1 rem to 1.125 rem, line-height 1.6, a measure of about 60 to 70 characters.
  5. Pull quote: serif, regular, 2 rem, line-height 1.2, tracking -0.01em. Pull quotes are short, so the serif earns its place again.
  6. Numerals: a stat such as "12" set large in the serif, with the caption in the sans below.

Then test the hierarchy with numbers, at a 16 px root. At a 1440 px viewport the headline clamp resolves to 104 px (8vw would be 115 px, capped at 6.5 rem) and the standfirst is 20 px, a ratio of 5.2. At 390 px, 8vw is 31 px, so the 2.75 rem minimum wins: 44 px against the same 20 px, a ratio of 2.2. Two lines of headline at line-height 1.02 are then about 90 px tall on the phone, still clearly larger than the standfirst. If the ratio fell below roughly 1.5, headline and standfirst would start to read as one level, and you would raise the headline's minimum or drop the standfirst a step.

What goes wrong

  • Two display faces competing. A decorative serif for headlines and a quirky sans for buttons splits attention. Keep one voice and make the other neutral.
  • A thin serif at small sizes. Hairlines vanish in captions, pull quotes on phones and anything over a dark ground. Move small text to the sans or pick a sturdier cut.
  • Default tracking everywhere. Large headlines look loose and uppercase labels look cramped until spacing is set per role.
  • Weights that skip. Regular headlines beside a semibold-only sans can look unrelated. Pick the sans weights from the same stem strength as the serif.
  • Numerals that clash. Dates, quantities and tables use figures constantly. Check that the sans offers tabular figures for columns (font-variant-numeric: tabular-nums switches them on where the font has them) and that the serif's numerals suit its capitals.
  • A size adjustment forgotten. After adding a scale factor or a font-size-adjust value, a headline that wrapped to two lines may wrap to three. Compare before and after at the narrowest width you support.

Files and loading

Each style is a file. A serif regular, a serif italic, and a sans with two weights already make four requests, fewer with variable fonts. Subset to the languages you need, preload only the one or two files used above the fold, and set fallback metrics so the swap does not shift the layout. Font loading without layout shift covers the mechanics.

Checklist

  • One job per face: serif to be seen, sans to be read.
  • Match x-height with measured numbers: each face's own aspect in font-size-adjust, and a font-size factor between the two faces. Recheck wrapping afterward.
  • Let optical sizing run, track display type negatively and small caps labels positively, and keep headline line-height near 1.
  • One italic phrase per heading, from a real italic file.
  • Test headline and standfirst together at desktop and phone widths.

Questions and answers

How many typefaces should a site use?

Two is enough for most sites: one for display, one for text and interface. A third is rarely justified, and every extra family and style adds files the visitor must download.

Should the serif or the sans be used for body text?

Usually the sans, because interface text, labels and small sizes favour a plain, open design. Reserve the serif for headings and short pull quotes, where its detail is visible.

What is an optical size axis?

A variable-font axis, tagged opsz, that redraws the letterforms for the size they are shown at. MDN notes that small sizes often get thicker strokes and larger serifs, and large sizes finer detail with more stroke contrast. It is on by default for fonts that have the axis (font-optical-sizing: auto).

Is faux italic acceptable?

Avoid it. When a family has no italic file, the browser may synthesize one by slanting the roman, which distorts the design. Load a real italic, or switch emphasis to a different device such as colour or weight. font-synthesis-style: none stops the slant.

Sources

  1. MDN: font-size-adjust developer.mozilla.org
  2. MDN: font-optical-sizing developer.mozilla.org
  3. MDN: font-synthesis developer.mozilla.org
  4. MDN: letter-spacing developer.mozilla.org
  5. WCAG 2.2: 1.4.12 Text Spacing w3.org