Design Inspector
- Extensions and automation
- Design Research
- 2026
- Open source
WXT, React, TypeScript, Tailwind CSS and 2 more
Design Inspector is a Chrome extension for reading how a website is designed. I hover an element to read its typography, colors, spacing, layout and assets, then click to pin it for the full reading. A side panel summarises the page: type scale, palette, spacing rhythm, radii, shadows, fonts and stack. Saved references stay on my machine. It is built with WXT, React 19, TypeScript and Tailwind v4.
The alternative was DevTools, or a row of single-purpose extensions, several of which asked for access to every site. Their weakness was trust. A font name read from CSS is a nickname someone typed, not a typeface. So the rule underneath this one comes from its PRD: evidence before certainty. Every reading is observed, derived, inferred or unavailable, and the interface says which.
pnpm installpnpm checkTest Files 50 passed (50) Tests 1144 passed (1144)pnpm exec playwright test --list | tail -1Total: 47 tests in 8 files# load .output/chrome-mv3 unpacked, Alt+Shift+I, pin a headingNB International Pro Regular by Neubau Berlindeclared as 'Nb international pro webfont', self-hosted
3 commands5 lines of output
- Chapter
- Extensions and automation
- Category
- Design Research
- Year
- Availability
- Open source
- Last commit
- WXT
- React
- TypeScript
- Tailwind CSS
- IndexedDB
- Playwright
Three font labels
lib/readings/fonts.ts gives every typeface one of three labels. Declared means the family is first in the CSS stack and nothing more. Matched means a loaded @font-face covers that family at the computed weight. Verified needs a matched face plus a measurement. renderCheck measures a string in the family against monospace and serif fallbacks, and only differing widths count as rendered. Equal widths are called a fallback only when document.fonts.check() also says the face is absent. Anything else is inconclusive. The check runs on a pinned element, never on hover. “Identify font file” then fetches the one served file in the background worker. It decompresses WOFF2 with brotli and reads the OpenType name and fvar tables for family, designer, foundry, licence and variable axes.
Detections carry evidence
The stack report in lib/readings/stack-signatures.ts holds 73 signatures across framework, builder, styling, motion, font provider and analytics. Each rule is strong or weak. One strong rule earns High, two weak rules earn Likely, and a single weak rule stays a hint that is never shown as a detection. Every detection lists the evidence behind it, and “Not detected” means insufficient evidence, not absence. Contrast follows the same rule. Over an image, a gradient or a video, computeContrast returns unavailable with its reason instead of a ratio. The page summary stays traceable too. Palette, type scale, spacing, radii and shadows each offer “Show on page”, which leads a value back to the elements that produced it.
Nothing runs until asked
The manifest declares no content scripts. lib/background/inspector-control.ts injects the inspector with chrome.scripting.executeScript only after I press the toolbar icon or Alt+Shift+I. Permissions stop at activeTab, scripting, sidePanel, downloads and storage, and a hook in wxt.config.ts strips the host permissions WXT would add. A still pointer does no work. Hover readings are shallow, and observers exist only while an element is pinned. Page scans run in requestIdleCallback chunks of at most 200 elements. e2e/perf-profile.spec.ts turns this into assertions, such as zero long tasks while idle. Saved references live in IndexedDB, and there is no analysis server and no telemetry.
Decisions and costs
- Inject on activation instead of on every page. Tabs I never inspect pay nothing. The cost is a separate
DI_E2E=1build that keeps host permissions for Playwright, and that build must never ship. - Fetch a font file only on request, from the host that already served it. The fetch lives in the worker, so a page’s CSP has no say and the decoder stays out of the content script. Each typeface costs one deliberate click.
- Reserve verified for a measurement. A face whose metrics match its fallback reads as inconclusive, which is honest and less satisfying.
- Leave response headers out of stack detection.
PageEvidence.headersstays empty, since observing headers would needwebRequest. Signatures visible only in headers go undetected, and that is the decision I would revisit with its own permission review.
Installing from source
It is not on the Chrome Web Store yet, so installing means building it with Node 24 and the pnpm version pinned in package.json.
pnpm install
pnpm check # vitest run, tsc --noEmit, then wxt build
Load .output/chrome-mv3/ through Load unpacked at chrome://extensions. On a page, Alt+Shift+I starts inspecting. Hover a heading, click to pin it, and walk its ancestors with ArrowUp and ArrowDown. Press “Identify font file” for the real typeface name. Open the side panel for the page summary, then save the pinned element with a note. The Saved tab exports Markdown, JSON, a taste-ledger fragment, or a ZIP with screenshots.
Status and limits
Version 0.1.0 is unpublished. .github/workflows/publish.yml submits to the Chrome Web Store on a version tag, and the first submission is pending. scripts/release.mjs bumps the version and builds the ZIP but performs no git operations. Browser pages, the Web Store, file:// URLs and PDFs are unsupported, and the popup says why. A Firefox build compiles, but store/FIREFOX.md lists five things that would break at runtime, and the port has not been attempted. The suite is 1,144 Vitest tests across 50 files, all passing when I ran them, plus 47 Playwright tests covering fixtures, accessibility and performance. It is MIT licensed.