I wanted a typeface of my own. What I did not want was to spend a year drawing one, so I started where the licence lets you start: Geist is released under the SIL Open Font License 1.1, and the OFL exists precisely so derivatives are allowed — provided the lineage travels with them.
So this is not a font I drew. It is a font I rebuilt. And the interesting part turned out not to be the drawing at all.
What actually changed
A rebrand would have been renaming the family and shipping it. That is not what happened. Proportions, x-height, character widths, tracking and kerning were all recalibrated, and the specific pairs that break first went through several rounds:
Oando, where a derivative usually starts to look off-balance firstm,iandn, because rhythm at small sizes lives or dies there- Figures, and the glyphs interfaces actually lean on
Each of those got corrected both visually and technically, more than once. What ships:
| Axis | What is in the box |
|---|---|
| Weights | Regular 400, Medium 500, SemiBold 600, Bold 700 |
| Formats | TTF, OTF, WOFF2, plus smaller Latin subsets for the web |
| Coverage | Extended Latin, Vietnamese, Cyrillic, UI symbols |
| OpenType | Figure features, including tabular |
| Dependencies | None, and no peer dependencies, since 0.6.1 |
The part nobody sees until it breaks
Changing outlines invalidates everything positioned against them. GPOS kerning had to be rebuilt, and so did the mark-to-base, mark-to-ligature and mark-to-mark anchors — every accent, every diacritic, re-anchored to contours that had moved.
Three more things that never show up in a specimen image:
- Combining marks keep a zero advance width. Get this wrong and accented text quietly gains spacing nobody asked for.
- The vertical metrics avoid clipping on Windows. Different platforms read different metric fields; the wrong combination crops ascenders on one OS and looks fine on yours.
- The TTF and WOFF2 builds are hinted for small sizes. A typeface that looks composed at 48px can fall apart in a table cell at 12.
None of that is visible in the poster you post. All of it is visible in a form field.
Treating it like software
Here is the shift that made the project work. Type projects are usually reviewed by looking at specimens — you render a page, you squint, you adjust. That catches what your eye is good at and misses everything else.
So the release criteria are checks, not impressions:
- FontBakery reports no FAIL, FATAL or ERROR across both the TTF and OTF builds. Not "mostly clean" — zero.
- Nine Playwright checks cover rendering from 10 to 18 pixels, baseline alignment, overflow, tabular figures and visual regression, in Chromium, Firefox and WebKit.
- The build is reproducible, so a release is something you can regenerate rather than something you found on a disk.
The 10-to-18-pixel band is the one I would argue for hardest. That is where interface text actually lives, and it is the range a specimen page never shows you.
Packaging is part of the design
The font ships as zutat-font on npm and as a full GitHub release, with the source and build pipeline public. The package carries four complete WOFF2 files, four Latin subsets, Next.js wrappers, TypeScript types and plain CSS exports.
Since 0.6.1 it has no dependencies and no peer dependencies, passes npm audit with no findings, and is published through GitHub Trusted Publishing with provenance.
A font package is a supply-chain artifact like any other. If you are adding one to a production app, being able to verify where the file came from is not a nice extra.
What AI did, and what it did not
AI assistance carried the mechanical half: transforming outlines, generating variants, working through repetitive corrections faster than I would have by hand.
What it did not do is decide. Which of four o variants sits right next to an n, whether the tracking reads too tight at 11px, when a correction has gone far enough — that stayed a judgment call every time, and that is where most of the rounds went.
The honest summary is that AI compressed the iteration cost, which let me do more rounds than I otherwise would have. It did not remove the need for someone to look at the thing and have an opinion.
What this doesn't settle
- It is a derivative, and that is a real constraint. Starting from Geist meant inheriting decisions I did not make. Some of the recalibration was working against the original rather than with it.
- Clean tooling is not the same as good type. FontBakery passing tells you the file is well-formed, not that the letters are good. Those are different questions and only one of them has a checker.
- One family, one designer, no critique. Type usually improves by being shown to people who draw type. Mine has not been.
Next
What I would take from this into any design work:
- Specimens flatter. Test at the sizes the thing will actually be used at.
- If a release criterion cannot fail, it is not a criterion.
- Packaging, provenance and audit status are part of the artifact, not paperwork after it.
- AI shortens the loop between attempts. It does not supply the taste that picks between them, which is the same split I route models on.
The open question is variable weights. Everything above is static, and a variable axis would make the whole metrics and hinting story considerably harder. I have not decided whether the payoff is worth it.
If you have shipped a typeface, or a derivative, I would like to know what your release checklist looks like, and whether anything on it caught something your eye had already signed off.
Zutat Sans is a modified typeface derived from Geist by Vercel, basement.studio and contributors, distributed under the SIL Open Font License 1.1. The name, spacing system, production pipeline, tests, documentation and packaging are the Zutat project's own.
Building something similar?
I write about setups I actually use. If you're working on something comparable, I'd be curious what your workflow looks like.