A dev-only overlay that scans the current page and flags icon-sized elements
that are rendered as <img> instead of inline <svg>.
Why: pulling icons in from Figma often lands them in the app as <img src="https://..."> rather than an inline, locally-bundled <svg>. That's two
problems at once — the icon isn't stored in the repo, and if the source URL
moves, times out, or the Figma MCP session expires, the icon silently breaks
into a missing-image glyph in production. icon-audit scans the rendered DOM
and draws a dashed outline over every icon it finds: green for inline
<svg> (bundled, safe), red for <img> (fetched, at risk).
It only activates in the Vite dev server. Do not import it from application
source (App.tsx, layouts, entry files).
npm install --save-dev icon-auditThat is the whole setup. Do not add <IconAudit /> or any icon-audit
import to your app.
On install, a plain "dev": "vite" (or "start": "vite") script is rewritten
to load the overlay through the icon-audit CLI. vite build is left
alone, so GitHub Actions / production Docker images do not need the package
on disk.
If your dev script is not a plain vite command, run npx icon-audit
instead of vite (same flags: npx icon-audit --host).
<IconAudit /> is a no-op leftover and does not mount the overlay.
The overlay writes uploaded custom packs to
.icon-audit/custom-packs.json when the Vite plugin is loaded (the CLI
does this for you). Without that, packs stay in this origin's localStorage.
Commit .icon-audit/ to share packs with teammates, or gitignore it to keep
them local.
To add the plugin yourself instead of using the CLI:
import { iconAudit } from "icon-audit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [iconAudit()],
});- A small dark toggle button appears in the bottom-left corner of the page.
- Click it (or press Cmd/Ctrl+Shift+I) to scan the page. Every element
classified as an icon gets a dashed outline — green for
<svg>, red for<img>— with anSVG/IMGbadge and a hover tooltip explaining why it was flagged and, for<img>icons, whether the source is local or remote. - Click a red highlight to open the replace panel. Tune width, height, and
color under the current icon, then search Lucide, Font Awesome
Solid, or Iconoir (~5k icons, bundled), or upload your own SVG pack. If the
same icon appears elsewhere on the page (same
src, or the same inline SVG glyph), a Select all button appears on the current-icon card. Click it to highlight every match and apply size, color, and the replacement to the whole set (click again to go back to just this icon). Hold Shift and click other highlights to add or remove individual icons from the selection — they don't have to match. Then Update to draft the swap on the page and queue an agent prompt (also copied to the clipboard) for Cursor / Claude Code / Codex. - Click a Draft highlight (or its purple badge) to unselect it: the original icon is restored and that queued prompt is removed. If several icons were drafted together, unselecting one rolls back the whole group. Deleting a prompt from the queue does the same restore.
- The pill toolbar shows counts and a close button.
Regular images — photos, illustrations, banners — are left alone. Only elements that look like icons are flagged.
Applying a replacement writes inline SVG into your source via the agent
prompt — no lucide-react / Font Awesome / Iconoir packages are required in
the target app. Catalogs are only used inside the icon-audit picker.
An element is classified as an icon if either of these match (either signal is enough):
- Size: the rendered box is small and roughly square — by default, both width and height ≤ 48px with an aspect ratio between 0.4 and 2.5.
- Naming:
alt,aria-label,class,id, or (for<img>) the filename insrccontain icon-ish wording (icon,glyph,chevron,caret, etc).
Pass overlay options through the Vite plugin:
iconAudit({
mount: {
enabled: true, // force on/off, overrides environment detection
position: "bottom-left", // "bottom-left" | "bottom-right" | "top-left" | "top-right"
shortcut: "mod+shift+i", // set to null to disable the keyboard shortcut
iconMaxSize: 48,
iconAspectRatioRange: [0.4, 2.5],
},
});Framework-agnostic mountIconAudit({ ... }) accepts the same fields plus
root (scan a subtree) and iconNamePattern.
You don't need to publish to npm to review overlay UI. example/ is a fake
icon-heavy dashboard (nav, KPIs, table actions, integrations). The Vite
plugin injects the overlay the same way a consuming app should — nothing in
App.tsx imports icon-audit.
npm install
cd example && npm install && cd .. # once — links the local package via file:..
# Fastest for CSS/layout tweaks: overlay source with Vite HMR
npm run example
# Same dashboard, but against dist/ — the files `npm publish` would ship
npm run example:dist
# Rebuild dist on save and reload the dashboard
npm run example:watchOpen http://localhost:5173. The header chip shows whether you're on source (HMR) or published dist/. Press Cmd/Ctrl+Shift+I (or the bottom-left toggle) to scan.
npm run generate:icons # rebuild Lucide / FA / Iconoir catalogs
npm run build # generate:icons + tsup -> dist/
npm test # vitest
npm run typecheckCatalog JSON under src/core/icons/generated/ is committed so consumers do not
need the icon pack packages at install time. Those packs are only
devDependencies used by generate:icons.
MIT