samlimby/icon-audit

A lightweight tool thats quick detection of incorrectly parsed icons through Figma MCP while allowing for the swapping out of svg icons all within development builds.

★ 0Forks 0TypeScriptGitHub ↗Compare

README

icon-audit

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).

Install

npm install --save-dev icon-audit

That 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.

Persist custom packs (optional)

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()],
});

Using it

  • 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 an SVG/IMG badge 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.

How "icon" is determined

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 in src contain icon-ish wording (icon, glyph, chevron, caret, etc).

Options

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.

Development

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:watch

Open 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 typecheck

Catalog 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.

License

MIT

Contributors

samlimby

Issues