Free font pairing tool: Two fonts, one voice.
Pick a heading font and a body font, then read them on pages people actually use: a park district homepage, a council agenda, a donation page. 28 free, open-source families. Take the CSS when it clicks.
Why it worksA soft, slightly wonky serif on top of the plain sans the US government drew for its own sites. Warm voice, no-nonsense reading.
Starting points
Readability
Nothing to flag. These settings read comfortably.
Sample: a park district homepage, set in Fraunces and Public Sans. The organization is made up.
Larkspur Aquatic Center
Summer starts at the pool.
Larkspur opens Saturday, June 6. Lap swim at 6 a.m., open swim from noon, and the lazy river runs until dusk.
Buy a season passSee pool hoursFall registration opens August 3 for residents and August 10 for everyone else.
This week in the parks
Toddler tumbling
Ages 2 to 4. Mondays, 9:30 a.m. Oak Room, Hollow Creek Center.
Mats, a balance beam and a parachute. Grown-ups stay and play. Six weeks for $48.
Pickleball open play
All ages. Daily, 7 a.m. to 10 a.m. Birchwood courts.
Bring a paddle or borrow one from the shed. First come, first served, four to a court.
Night hike at Hollow Creek
Families. Friday, 8 p.m. Meet at the nature center.
A naturalist leads a slow loop to listen for owls and look for the first fireflies. Free.
Take it with you.
The CSS
/* Fraunces + Public Sans, from prolificdigital.com/tools/type-pairing
Self-host with Fontsource:
npm install @fontsource-variable/fraunces @fontsource-variable/public-sans
then import each one once, e.g. in your root layout:
import "@fontsource-variable/fraunces";
import "@fontsource-variable/public-sans";
Both are free under the SIL Open Font License 1.1. */
:root {
--font-heading: "Fraunces Variable", "Fraunces", ui-serif, Georgia, "Times New Roman", serif;
--font-body: "Public Sans Variable", "Public Sans", system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
/* Perfect fourth scale (1.333), 18px body.
Fluid from 16px and 1.216 on a 375px screen
to 18px and 1.333 at 1280px and wider. */
--step--1: clamp(0.8221rem, 0.813rem + 0.039vw, 0.844rem);
--step-0: clamp(1rem, 0.9482rem + 0.221vw, 1.125rem);
--step-1: clamp(1.2165rem, 1.0991rem + 0.501vw, 1.4996rem);
--step-2: clamp(1.4798rem, 1.2646rem + 0.918vw, 1.999rem);
--step-3: clamp(1.8rem, 1.4418rem + 1.529vw, 2.6647rem);
--step-4: clamp(2.1897rem, 1.6252rem + 2.409vw, 3.552rem);
--step-5: clamp(2.6636rem, 1.8054rem + 3.662vw, 4.7348rem);
--measure: 66ch;
}
body {
font-family: var(--font-body);
font-size: var(--step-0);
font-weight: 400;
line-height: 1.6;
}
h1, h2, h3, h4 {
font-family: var(--font-heading);
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.01em;
text-wrap: balance;
}
h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }
small { font-size: var(--step--1); }
p, li, blockquote {
max-width: var(--measure);
}
Headings
Fraunces
Free under the SIL Open Font License 1.1
Fraunces on FontsourceFraunces on Google Fonts
npm install @fontsource-variable/frauncesBody
Public Sans
Free under the SIL Open Font License 1.1
Public Sans on FontsourcePublic Sans on Google Fonts
npm install @fontsource-variable/public-sans
How the shuffle picks
Different shapes.
A serif with a sans, a slab with a sans, a display face with a reading face. Two fonts of the same kind look like a mistake, so the shuffle never offers them.
A shared mood.
Every family is tagged with how it feels: civic, warm, editorial, technical and so on. Pairs that share a mood score higher, so a contrast still sounds like one voice.
A body font built for reading.
Display faces stay in the headings. Reading faces with a generous x-height (the height of the lowercase) get a nudge up, because they stay clear at small sizes.
Mix anything you like with the menus; the shuffle just won’t suggest a pair we’d talk you out of. Every font is hosted here, on our own server, and loads only when you choose it. Font licences and notices.
Questions
Type, explained.
Contrast and agreement at once. The two should be clearly different in shape, such as a serif with a sans serif, so readers can tell headings from text at a glance. They should agree in mood and proportion, so the page sounds like one voice. Similar x-heights and a similar feel (both warm, both crisp) usually matter more than any rule about classification.
Yes. Every family here is released under the SIL Open Font License 1.1. You can use them on websites, in print and in apps, commercial work included, and you can host the files yourself. What you can’t do is sell the fonts on their own. Each family’s licence is linked from the tool, and the notices for the copies we host are in our LICENSES file.
Your visitors’ browsers then only talk to your own site, not to a third party, which is simpler to explain in a privacy policy and one less outside dependency. It used to be argued that a shared cache made Google-hosted fonts faster, but browsers now keep a separate cache for each site, so that advantage is gone. Fontsource packages make self-hosting a one-line install, and this page does the same thing: every font loads from our own server.
16px is a sensible minimum on the web, and 17 to 20px often reads better, especially for serifs and for fonts with a small x-height. WCAG doesn’t set a minimum size, but it does expect text to survive being zoomed to 200% (1.4.4) and having its line, letter and word spacing increased (1.4.12), so set sizes in rem and avoid fixed heights on text boxes.
Somewhere between 45 and 75 characters, with about 66 as the classic target. Much longer and readers lose their place going back to the start of the next line; much shorter and the eye keeps jumping. WCAG’s AAA guidance (1.4.8) caps it at 80. In CSS, a max-width in ch units (66ch) is the simplest way to hold it.
A set of sizes made by multiplying the body size by one ratio, again and again. With 18px text and a 1.333 ratio (a perfect fourth), the steps are 24, 32, 43, 57 and 76px. One ratio keeps headings related to each other. The CSS here makes each step fluid with clamp(), so headings shrink on phones without media queries.
No. It runs in your browser. Your choices sit in the page’s address so you can share a link, and the only things it downloads are the font files you pick, from our own server.
Fonts are the easy part.
Type is one decision in a hundred. We find what makes an organization different, then design and build a site that could only be theirs, type included.