Every font pairing looks good at 72px. Try it on a form label.
Typebed drops two Google Fonts into a whole fake product, then scores the pairing from the glyphs your browser actually rasterized. No lookup table of blessed combinations.
Open any font-pairing tool and you get the same view: one enormous headline sitting over a paragraph of filler. That view flatters everything. Almost no typeface looks bad at 72px with room to breathe, which is exactly why the preview tells you nothing.
Type shows its problems when it has to do small, dense, unglamorous work. A 13px form label. A button caption. The fine print under a trial CTA. Numerals lined up in a pricing card. Plenty of pairings that look sharp on a specimen sheet go soft down there: a body face with a low x-height turns mushy next to a display face, or two sans faces that seemed distinct in isolation stop separating once one of them is a heading and the other is the paragraph beneath it. You find this out after the fonts are in the codebase, because the only way to find it out sooner was to build the mockup first, and nobody does that to audition a font.
Typebed skips to the end. Pick two Google Fonts, one display and one UI, and instead of a specimen it renders a whole fake product in them: a nav bar, a marketing hero with a stat row, a three-tier pricing grid, and a signup form with labels, helper text, an error state, and a disabled button. The small type is sized honestly at 12px to 13px, because that is where pairings fail and a preview that avoids it is lying to you.
The score comes from the glyphs, not a list
Alongside the preview there is a readability score out of 100. The interesting part is where the numbers come from.
Most tools that rank pairings are working off a curated list: someone decided Fraunces and Inter
go together, and the tool knows that fact. Typebed has no such list. It measures the fonts your
browser just rasterized, using the canvas text API. Draw an x, ask for its
actualBoundingBoxAscent, and you have the rendered x-height in pixels. Do the same for an H
and you have the cap-height. The ratio between them is roughly "how much of this face's visual
mass is in lowercase," which is the single best cheap predictor of whether body copy survives at
13px. A face with a tall x-height stays readable when it shrinks; a face with a short one hollows
out.
Three factors go into the number. Text contrast, the WCAG 2.x ratio of the preview's text against its background, carries 0.40, because it is the only factor with an objective pass or fail. Body legibility, that measured x-height over cap-height, carries 0.30. Pairing contrast, how far the two faces' metrics and categories actually diverge, carries the other 0.30. Each one shows its own plain-language verdict, so the score is an argument you can check rather than a bar you have to trust.
Because it measures rather than looks up, it works on any pairing on Google Fonts, including the one you invented five seconds ago.
The rule that was hardest to get right
The first scoring pass let any weak factor cap the total. It seemed principled. It also condemned Oswald + Inter, which is a genuinely good pairing: two sans faces whose widths sit far enough apart that the hierarchy holds. The heuristic said no. The eyes said yes. The eyes were right.
So now only objective defects cap the score: text that fails WCAG, and the same family in both slots. Legibility and pairing contrast steer the number through their weight and explain themselves, but they never veto, because canvas metrics cannot see a typeface's skeleton. Oswald scores a 78 and keeps its dignity. Roboto + Open Sans gets a 72 and a flag for being too alike, which is the more useful failure to catch anyway. And when the glyphs cannot be measured at all, the score falls back to contrast only and says so, rather than inventing a number to fill the gap.
One detail I care about more than it deserves: swapping a font never flashes. The current face keeps painting until the next one has finished downloading, so the layout does not blink at you mid-comparison. The whole thing is a static bundle with no backend, both fonts and the theme live in the URL, and saved pairings sit in localStorage synced across tabs.
Try it
Open Typebed, which lands on Fraunces and Inter at 90, and change the UI font to something with a short x-height. Watch the signup form's helper text go soft while the hero stays fine, and watch the legibility factor explain why. Then copy the address bar and send the pairing to whoever has opinions about it. The source is on GitHub.
This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.
Loading comments…