I built brandmd 6 months ago. It is an npm CLI that extracts any website's design system into a DESIGN.md file. The idea: drop the file in your project root, AI coding agents (Claude Code, Cursor, Gemini CLI, Stitch) read it, and they generate on-brand UI instead of generic shadcn-default.
It was working. Stripe, GitHub, Vercel, Linear — primary fonts came back correct. Then a user DMd me last week.
"your tool says my primary font is Inter. it is actually Manrope. the brand is on the hero, not the paragraphs."
Fair. So I ran brandmd against 100 popular design system sites to see how widespread the bug was.
The result
9 of 45 successfully-extracted sites returned the wrong Primary font. 20%.
| Site | brandmd said | Reality |
|---|---|---|
| mantine.dev | Menlo | Outfit |
| remix.run | JetBrains Mono | Inter Variable |
| neon.tech | GeistMono | Inter |
| valura.ai | Inter | Manrope |
| railway.app | Inter | IBM Plex Serif (on the hero) |
| resend.com | inter | aBCFavorit |
| svelte.dev | Georgia | DM Serif Display |
| workday.design | Times | (no real brand font, fallback) |
| htmx.org | Times | (intentionally minimal) |
Three different failure modes:
- Code fonts winning. mantine and remix had so much code on their homepage that JetBrains Mono / Menlo dominated the font frequency count.
- Body text drowning the brand. valura uses Inter on 737 elements (utility), Manrope on 28 (hero). Frequency rank picks Inter.
- Fallback Times. Sites with no custom font load Playwright's default fallback. Old logic surfaced Times as Primary.
Root cause
// analyze.js, v0.7.2
const primaryFont = fontList[0]?.[0];
That was it. Primary was just the most-frequent font across all DOM elements. Body text always wins.
The fix (v0.8, just shipped)
// analyze.js, v0.8
const primaryFont =
pickNonExcluded(displayFonts) || // fontSize >= 40px (hero)
pickNonExcluded(headingFonts) || // h1-h6
pickNonExcluded(bodyFonts) || // p, li, span, div
fontList[0]?.[0];
// excluded from Primary: mono fonts (Menlo, JetBrains Mono, GeistMono, etc.),
// default fallbacks (Times, Arial, Georgia), icon fonts (Material Icons,
// Font Awesome, Heroicons).
Three principles:
- Display first. The hero font is the brand font. fontSize >= 40px is where the brand lives, not in h2/h3 subtitles.
- Skip mono. Code fonts can win Secondary slot, never Primary. mantine.dev wants Outfit on top, JetBrains Mono in code blocks.
- Skip fallbacks. If the site has no real custom font, surfacing Times honestly is fine. But if there ARE real fonts available, use those.
Also shipped:
-
Quote-aware font-family parser (handles
var(--font, 'Inter')and backslash escapes that v0.7.2 split incorrectly) - Per-role cascade (heading falls back to display falls back to body)
- Navigation timeout 30s → 45s for slow SPAs
Results
7 of 9 wrong-Primary cases now report the correct brand font. The remaining 2 (railway.app, workday.design) genuinely have no distinct brand font on the hero, so the output is honest.
Install
npm i brandmd
# or run without install
npx brandmd https://your-site.com
Repo: github.com/yuvrajangadsingh/brandmd
If brandmd saved you time, star the repo — it helps other devs find it.
Top comments (0)