Case Study · Privacy-First Chrome Extension

The site asked for one font.
Visitors are seeing another.

Every font inspector reads the CSS and tells you what the page asked for. FontLens reads the CSS and checks what the browser actually painted — so when "Söhne" fails to load and everyone's seeing Arial, an amber dot says so. It runs entirely on your device, makes zero network calls, and asks for no host permissions.

See the source
Muzeeb Urrahaman Solo designer + builder v1.0.0 · Web Store ready MV3 · Chrome / Brave / Edge
1
Thing no rival shows: the fallback
0
Network calls, ever
4
Permissions · no host access
249
Automated tests (230 unit + 19 e2e)
~70KB
Total shipped bundle
3
Copy formats: CSS · Tailwind · Token
In one line

FontLens is a Chrome extension that hovers to reveal any element's real rendered font, click-stamps a card for every style on the page, and — the part no other tool gets right — flags when the font a site requested has silently fallen back to a substitute, all on your device with zero network calls.

Every designer and front-end developer has hit this: the mockup says Söhne, production looks slightly off, and the usual font tools happily report "Söhne" because that's what the CSS asked for. What visitors on a slow network or a blocked CDN actually see is Arial — and nobody tells you. FontLens reads the requested face and checks what the browser truly painted, then marks the gap with a single reserved amber signal. Everything else — the type-system extraction, the copy-as-code, the variable-font sliders — runs locally in the browser, with no account, no telemetry, and no server in the loop.

The signal was my own bug, then a teardown. I lost an afternoon to a design that looked "slightly wrong" in production before realising the webfont had silently fallen back to Arial. So I checked every font inspector I could find — and every one reported the requested face, none the rendered one. That gap defined the scope: build the single check they all miss, and ruthlessly strip everything that isn't in service of trusting it.

Problem

  • Fonts fail silently. A blocked CDN or slow network swaps your typeface for a fallback and the page never says so.
  • Existing inspectors only read the CSS — they report the font that was requested, not the one on screen.
  • Handing type from design to code means re-typing sizes, weights and line-heights by hand.

Solution

  • Compares requested vs actually-rendered face and flags the gap with a reserved amber dot.
  • Hover to read any element; click to pin cards and extract every type style, grouped by family.
  • Copy any style as CSS, Tailwind, or a design token in one click.

Results

  • 249 automated tests (230 unit + 19 end-to-end); QA scored 97/100 with zero failures.
  • Zero network calls — no analytics, no remote code, no host permissions. Enforced by a build-time bundle audit.
  • v1.0.0 packaged at ~70KB, with a complete Chrome Web Store publish kit.
The wedge
Why this exists

Everyone reads the CSS. Reading what actually rendered is the whole product.

Point any popular font inspector at a page and it will confidently name the font — because it's reading the declared font-family off the stylesheet. But a declaration is a request, not a guarantee. On a slow connection, a blocked CDN, a corporate proxy, or a font that simply failed to load, the browser quietly reaches for the next name in the stack. The declared answer and the painted answer diverge, and the tool never notices.

FontLens's bet is that this exact gap is the most valuable thing a font tool can surface. It measures the requested family, then inspects what the browser truly rendered, and when they differ it raises one thing: a reserved amber signal that means only "the requested font isn't loading." Everything else about the product — the restrained grayscale UI, the copy-as-code, the type-system view — is in service of making that one honest signal trustworthy.

The amber accent has exactly one meaning. In the entire design system, the amber token is reserved exclusively for the fallback signal — never for hover, warnings, info, or decoration. So when you see amber, it always means the same thing. And FontLens follows the voice rule too: when detection is genuinely uncertain it says "specimen approximate" rather than inventing an answer.
What it shows
The job, plainly

Eight things FontLens shows you.

Each one answers a real typography question — with the fallback check woven through every surface. Hover for a glance, click for depth.

The wedge

Requested vs actually-rendered

When the page asks for one face and the browser painted another, an amber dot flags it — the one thing WhatFont and friends miss.

Hover chip

Read any element in a glance

A small floating chip follows the cursor showing the rendered font, weight, size, line-height and colour. No clicking required.

Pin cards

Click to stamp, compare side by side

Click text to pin a persistent, expanded card at that spot. Pin as many as you like and compare a whole page's type at once; Esc clears them.

Type system

Every distinct style on the page

Click to extract each type style, grouped by font family and sorted by usage — with fallbacks surfaced first so problems rise to the top.

Page banner

A page-level fallback warning

If any font on the page is falling back, a full-width amber banner says so up top — e.g. "1 of this page's fonts isn't loading — visitors see a fallback."

Copy as code

CSS · Tailwind · design token

Hover a row to reveal copy buttons. One click puts the style on your clipboard in the format you picked — no re-typing sizes by hand.

Variable fonts

Audition axes live on the page

For variable faces, sliders let you preview wght, wdth, optical size and more in real time, with a Reset link.

Source badge

Where the font comes from

Each family is tagged Google, Adobe, Self-hosted, System, Variable, or Fallback — so you know the delivery path at a glance.

How it works
The flow

Inspect locally, compare locally, copy locally.

There's no server in the loop. You trigger it on the tab you're looking at; a content script reads the page, a detection engine compares requested against rendered, and the side panel renders the result. Here's the whole path:

1

You open it on the current tab

Click the FontLens icon or press Alt+Shift+F. Under activeTab, the extension injects only into the tab you asked for — never in the background, never on other sites.

2

It reads the real computed styles

A content script and a Shadow-DOM overlay read each element's computed font, weight, size, line-height and colour — isolated from the host page's own CSS so nothing bleeds either way.

3

The engine compares requested vs rendered

lib/detector.js and lib/render-detect.js work out the declared family and the face the browser actually painted. If they diverge, the row is marked a fallback — the reserved amber signal.

4

The side panel builds the type system

Extraction groups every distinct style by family, sorts by usage, tags each with a source badge, and floats fallbacks to the top. Variable faces get live axis sliders.

5

You copy exactly what you need

Hover any row for copy buttons — lib/snippets.js emits CSS or Tailwind, lib/tokens-export.js emits a design token — or download the whole token set. Your theme and default format persist in chrome.storage.local.

The permission model

Four permissions, and deliberately no host access.

FontLens asks for the minimum a tab-inspection tool needs — and pointedly omits host_permissions. It cannot run anywhere until you invoke it on a tab. Here's exactly what each one is for:

● activeTab

Inspects only the tab you focus when you click the icon or hit the shortcut. No background access.

user-initiated only
● scripting

Injects the content script and Shadow-DOM overlay on your action. No remote code — everything ships in the bundle.

local scripts only
● sidePanel

Opens the panel that shows the type system, so it can persist across page clicks while you inspect.

on-device UI
● storage

Remembers your theme and default copy format in chrome.storage.local — never transmitted.

local only
FontLens side panel showing the full type system for a page, with a fallback banner and a Söhne to Arial fallback card at the top
The side panel — every distinct type style on the page, grouped by family and sorted by usage. The amber banner and the "Söhne → Arial · Fallback" card show the one thing other tools miss: the requested font isn't loading, so visitors see Arial.
Others vs FontLens
The difference that matters

Same question, one extra check.

A CSS-only inspector and FontLens both answer "what font is this?" The difference is whether the tool stops at what was requested or goes on to verify what actually rendered. On the left, the common approach; on the right, FontLens's:

The CSS-only inspector
// 1. Read the declared font-family
const declared = getComputedStyle(el)
                  .fontFamily;

// 2. Report the first name in the stack
show(declared.split(",")[0]);
//    → "Söhne"  ✓ looks correct…

// 3. …but never checks what painted.
//    If the CDN was blocked, the user
//    is actually seeing Arial — and
//    the tool has no idea.
How FontLens works
// 1. Read the requested family…
const requested = parseStack(el);

// 2. …then measure what truly rendered
const rendered = detectRenderedFace(el);

// 3. Diverged? Raise the ONE signal.
if (requested !== rendered)
  row.flagFallback(requested, rendered);
//    → "Söhne → Arial · fallback" ⬤

// 4. Local only. Zero network calls.

The trade-off, stated honestly: rendered-face detection is a heuristic, not magic — closed shadow roots and some cross-origin iframes can't always be measured. When FontLens can't confirm, it says so ("specimen approximate") instead of asserting a wrong answer. In exchange you get the one check no CSS-only tool performs, on a tool that never phones home.

The same on-device engine powers every surface. These are the real store shots:

The hard parts
The judgment calls

The interesting decisions were about restraint and honesty.

Anyone can list the fonts on a page. The decisions that make FontLens trustworthy were mostly about what not to do — and about being honest where detection is hard. Four real ones:

The detection engine is the moat. The value isn't the UI chrome — it's lib/detector.js, render-detect.js, source-classify.js and the variable-axis logic that reliably tell requested from rendered across messy real-world pages. That measurement layer, hardened by 249 tests, is the slow part for anyone to copy.
The stack
Under the hood

A Manifest V3 extension, built to be audited.

No framework and no build-time magic to hide behind — plain ES modules a reviewer can read top to bottom, with pure logic separated from anything that touches the DOM. Every file has one job:

File / folderWhat it does
manifest.jsonMV3 config. Permissions: activeTab, scripting, sidePanel, storage. No host_permissions — cannot run until you invoke it.
service-worker.jsThe ephemeral background worker — opens the side panel, wires the toolbar action and the Alt+Shift+F command, and routes messages.
content/overlay.js · content.jsThe only surface with page DOM access. Renders the Shadow-DOM hover chip and the click-to-pin multi-card overlay.
sidepanel/*The Side Panel root — panel.html/.css/.js/.render.js: glass header, hover/inspect toggle, grouped family cards, fallback banner, theme toggle, token download.
lib/detector.js · render-detect.jsThe wedge. Works out the requested family and the face the browser actually painted, and decides when it's a fallback.
lib/extractor*.jsWalks the page (plus shadow roots and same-origin iframes) to collect every distinct type style, grouped and usage-counted.
lib/snippets.js · tokens-export.jsEmit a style as CSS, Tailwind classes, or a design token. source-classify.js tags each family's origin (Google / Adobe / self-hosted / system).
lib/variable-axes.jsParses variable-font axes and drives the live weight / width / optical-size sliders.
build/audit-bundle.jsFails the build if the zip contains remote code, network calls, or a stray host permission — the zero-network promise, enforced.
test/ (vitest + Playwright)230 unit tests beside their source plus 19 end-to-end specs against the real unpacked extension — including an AA colour-contrast assertion.
Try it in two minutes

Load it unpacked, hover a page, done.

No login, no account, no setup. The fastest way to see everything — including the fallback signal — working:

# 1. Open chrome://extensions, enable Developer mode,
#    click "Load unpacked" and pick the repo root.

# 2. Click the FontLens icon (or press Alt+Shift+F).
#    The side panel opens.

# 3. Hover any element for the chip; click text to pin a card.

# 4. Visit a site with a blocked/failed webfont to see the
#    amber fallback signal fire — the part nobody else shows.
stripe.com   # Söhne → Arial when the face doesn't load
Results
Where it stands

Shipping, verifiable, and honest about scope.

FontLens v1.0.0 is feature-complete and Web Store ready, with a full publish kit — listing copy, privacy answers, a hostable policy, and generated screenshots and promo tiles. Everything in the feature list is working code exercised on every run by a 249-test suite, and because the whole thing is source-available, every privacy claim can be checked line by line.

The unglamorous states are designed, not skipped. The uncertain state is a first-class feature — "specimen approximate" whenever a shadow root or cross-origin iframe can't be measured. The side panel's empty state (nothing pinned yet) prompts you to hover or click instead of showing a blank box, and a page with no detectable webfonts says so plainly rather than rendering an empty list.
249

tests, green

230 vitest unit tests beside their source plus 19 Playwright end-to-end specs against the real unpacked extension. A dedicated test asserts AA colour contrast.

0

network calls, by design

No analytics, no remote code, no telemetry, and no host permissions. The claim is enforced by a bundle audit that fails the build if it's ever broken.

97

/100 QA score

A full functional-plus-design QA pass (A1–A10, B1–B7) scored 97/100 with zero failures, alongside a clean security review.

Reflection
Reflection

Where it can't be certain — and what that cost.

The tempting shortcut was to always return an answer: read the CSS, measure what I could, and quietly guess the rest. I rejected it — a confident wrong font is worse than an honest "I can't tell." Closed shadow roots and some cross-origin iframes genuinely can't be measured, so instead of guessing, FontLens renders the specimen in a matching generic and tags the row "specimen approximate." It's a weaker-looking demo, but a tool you can trust.

What I'd do differently: I over-invested in the type-system extraction and copy-as-code before hardening the fallback-detection edge cases — which are the actual reason the tool exists. Next time I'd push the one differentiating signal to its limits first, then add the surrounding convenience features.

What's next
What I'd do next

Ordered by value, not novelty.

The through-line. Every future feature follows one rule: local-first, zero network, minimum permissions, and never assert a font it can't confirm. The privacy-and-honesty stance isn't a phase — it's the reason the tool reads truthfully in the first place.
Interview brief
Anticipated questions

The things people probe for.

Isn't this just WhatFont with a fresh coat of paint?
No — it does the one thing WhatFont doesn't. WhatFont and most inspectors read the declared font-family and report the requested font. FontLens reads the request and measures what the browser actually rendered, so it catches silent fallbacks (Söhne asked for, Arial painted) that CSS-only tools report as fine. That fallback detection is the wedge; everything else is table stakes done cleanly.
How does it detect the rendered font without any network access?
Entirely in the page. A content script reads each element's computed styles, and the detection engine (lib/detector.js + render-detect.js) compares the requested family against measurements of what actually painted. It's all local browser measurement — no font fingerprinting service, no server, no outbound request.
What happens when it genuinely can't tell?
It says so. Closed shadow roots and some cross-origin iframes can't always be measured. Rather than guess, FontLens renders the specimen in a matching generic face and tags the row "specimen approximate." Honest uncertainty is a design rule here, not an afterthought — the voice guidelines forbid asserting a font it can't confirm.
Why no host permissions? Wouldn't the tool be more useful with them?
It would run automatically on every site — and be far harder to trust. FontLens uses activeTab, so it only ever touches the tab you invoke it on, and only when you click the icon or press Alt+Shift+F. A build-time bundle audit fails the release if a host permission is ever added. The narrower scope is the point.
You say "zero network calls." Is that literally true?
Yes. There's no analytics SDK, no remote code, no telemetry beacon anywhere in the bundle, and build/audit-bundle.js fails the build if any appear. The optional "notify me" for future features is a stub only; nothing about your browsing or the pages you inspect leaves your machine.
Can I get the styles into my codebase quickly?
That's the copy-as-code feature. Hover any row in the side panel and one click puts the style on your clipboard as CSS, Tailwind classes, or a design token — whichever you set as default in options. You can also download the full token set for the page. No re-typing sizes, weights and line-heights by hand.
Can I trust the privacy claims?
You don't have to trust them — the source is available and the four-permission model is right there in manifest.json, each with a documented single-purpose justification. The zero-network promise isn't a slogan; it's asserted by an automated bundle audit on every build.