
Left to right: the design, an agent building by eye, an agent using figma-map's tokens + verify loop. Same model, same prompt, same assets — the only variable is the tool.
| Case |
Design |
by eye |
figma-map |
Independent pixel diff |
| Landing hero |
decorative, photo + gradient |
7.83% |
4.10% |
~48% closer |
| Landing hero 2 |
dark hero, feature cards |
8.17% |
5.98% |
~27% closer |
| Admin dashboard |
real component-dense UI (sidebar, cards, charts, list) |
8.87% |
1.92% |
~78% closer |
Same agent, same model, same prompt, same shared assets — only the tool differs. By eye: the agent gets a screenshot and "build this." figma-map: the agent gets the Figma selection through the bridge, builds from figma-map's tokens/plan, then renders and compares its own output with figma-map's reconcile (plus its own browser MCP tools) until the diff closes. The score itself is not figma-map's — a plain, independent pixel diff against the reference image, so it can't be biased toward the treatment arm. Method & caveats →
What it is
// verify reconcile 55:1140 --story cta-banner
{ "match": false, "remaining": 2, "byElement": [
{ "nodeId": "55:1140", "name": "CTA", "diffs": [
{ "prop": "background-color", "is": "rgb(31,41,55)", "should": "#18181b" },
{ "prop": "padding-left", "is": "12px", "should": "16px" } ] } ] }
That's the primitive everything else is built on: not "does this look
right," but exactly which element, which property, which value — an agent
can act on that and loop until it's gone.
- A closed verify loop, not a screenshot to eyeball. figma-map renders your
implementation, reads its actual DOM, and diffs computed styles against the
design's exact tokens — per-element
is → should numbers the agent can fix,
not vibes to interpret.
- Ground truth before vision, everywhere. Structure, tokens, and component
identity are read straight from Figma's data model whenever it has the
answer. A vision model only steps in for the one question Figma's data can't
answer — which code component is this? — never as the default.
- AI runs once, codegen runs forever. That one vision-dependent question is
answered once, into a reviewable binding file. Every generation and
verification pass after that is deterministic, repeatable, CI-friendly code —
no LLM in the hot path.
- MCP-native. Point an agent (Claude Code, Cursor, …) at it and it gets the
full CLI as typed tools, identical surface, zero drift.
- Closes the loop on live pages too. A browser extension lets a human flag
a mismatch on a running page and link it straight to its Figma node — the
agent picks that up as structured ground truth, never a raw pixel guess.
A human clicks the element that's off, notes what's wrong, and sends it — the agent gets a Figma node id and bounds, not "the card looks weird."
Why
Pointing an agent at a Figma screenshot and asking it to "build this" mostly
works — until it doesn't, and there's no way to tell how far off without a
human eyeballing a diff. The agent picked a color close enough, a padding
that's 4px short, the wrong variant of your <Button> — and nothing in the
loop can tell it that, so it stops when it looks done, not when it is
done.
figma-map closes that loop. It reads Figma's actual data — not a rendered
picture of it — for structure, tokens, and (via a one-time binding) component
identity, and it can re-render the agent's own output and diff it against
that same ground truth, property by property. The agent gets exact numbers to
fix, not vibes to interpret, so the loop actually converges on the design
instead of stalling on "close enough."
How it works
build plan → a buildable spec for a Figma node: layout, each
component instance mapped to your code (import + props), exact tokens.
- The agent writes the code, tagging each element so it can be measured
later.
- The agent renders it (Storybook or a dev server).
verify reconcile renders the implementation, reads its DOM, and diffs
it against the design's exact tokens — per-element is/should numbers.
- The agent fixes the exact properties and loops from step 3 until
everything matches.
A ready-made agent skill ships in .claude/skills/figma-map
that teaches Claude Code this loop automatically. For the full request-flow
diagrams and the "ground truth before vision" decision tree, see
docs/architecture.md.
Component identity, solved once
The one place vision is unavoidable is matching a Figma instance to your
code component — Figma's data model has no field for "this is our
<Button>." figma-map solves it once, up front, into a reviewable
figma-map.binding.yaml you can correct by hand. Every generation after that
is deterministic:
Storybook ──scan──▶ catalog (screenshots + import paths, no AI)
Figma ──bind (vision LLM, once)──▶ figma-map.binding.yaml ──review──▶ map (deterministic) ──▶ JSX
Install
1. Install the CLI
curl -fsSL https://raw.githubusercontent.com/KirillBaranov/figma-map/main/install.sh | sh
Detects your OS/arch, downloads the matching release, verifies its SHA-256
checksum, and installs the binary. Override with FIGMA_MAP_VERSION=v0.1.0
to pin a tag, or FIGMA_MAP_INSTALL_DIR=~/bin to choose the directory.
Alternatives: go install github.com/kirillbaranov/figma-map@latest, or grab
a prebuilt archive from the releases page.
Updating: once installed, figma-map update fetches the latest release
for your platform, verifies its checksum, and replaces the running binary in
place — no need to re-run the install script. figma-map update --check
just reports whether a newer version exists; figma-map update --version v0.6.0 pins a specific tag.
2. Load the bridge plugin in Figma
The bridge is what lets figma-map read your open Figma file directly, with no
API token and no rate limits.
figma-map bridge up --repo <path to this checkout> # builds + starts the local backend on :1994
Then, once, load the plugin into Figma:
- Open your Figma file (desktop app).
- Plugins → Development → Import plugin from manifest…
- Select
extensions/plugin/manifest.json from this checkout.
- Run it once (Plugins → Development → Figma MAP Bridge) — it connects
over WebSocket to the backend you just started and stays connected while
the file is open.

Connected, and it already wrote the exact prompt for your agent — file, selection, node id.
3. (Optional) Load the browser extension
Lets a human flag a mismatch on a running page and hand it to the agent as a
Figma-linked issue, instead of a screenshot and a paragraph of description.
cd extensions/browser && npm install && npm run build
- Open
chrome://extensions, enable Developer mode.
- Load unpacked → select
extensions/browser/dist.
Requirements
The only hard requirement is the Figma plugin (step 2 above) — everything
else below is optional, and just means a specific feature won't work
without it.
| Dependency |
Required? |
Without it |
| Figma desktop, bridge + plugin running (step 2 above) |
Yes |
Nothing works — this is how figma-map reads your file at all. |
| Google Chrome / Chromium |
Optional |
No headless rendering — screenshot, verify reconcile, and the browser-extension compare loop need it. |
| Storybook 7+ running |
Optional |
No code-component catalog — setup scan/build map (going from a Figma node to your JSX) need it. Reading tokens/structure straight from Figma doesn't. |
| OpenAI-compatible vision endpoint + key |
Optional |
No component matching or prop inference — setup bind and the leftover-prop vision step need it (works with OpenAI, a local Ollama/llava server, or any compatible gateway). |
| Browser extension (step 3 above) |
Optional |
No human-flagged live-page issues — everything else still works without it. |
Quick start
figma-map init /path/to/your/project # skill, figma-map.yaml, MCP registration, CLAUDE.md
cd /path/to/your/project
export OPENAI_API_KEY=sk-...
figma-map doctor # verify bridge, chrome, storybook, key
# 1. Build the code-component catalog (no AI).
figma-map setup scan --project /path/to/storybook-project
# 2. Match Figma to the catalog and write the binding (AI, run once).
figma-map setup bind
# → review figma-map.binding.yaml
# 3. Generate code for any Figma node.
figma-map build map 13:1077
init never clobbers what's already there — it prints exactly what it's
about to create/change and asks for confirmation first (-y to skip that for
scripts). Full command list and every flag: docs/commands.md.
Troubleshooting
Bridge disconnected? This is almost always it: Figma freezes plugin
execution (including the WebSocket connection) when the Figma window loses
focus or is minimized — that's Figma's behavior, not a figma-map bug. If
the bridge drops, you (or the agent) most likely had Figma closed, in the
background, or minimized for a while. Bring Figma back to the
foreground — the plugin reconnects on its own — and have the agent retry
the call. No restart of the backend needed.
Other common issues:
figma-map doctor fails on "bridge" — the backend (:1994) isn't
running, or no plugin instance has ever connected to it. Run
figma-map bridge up --repo <path>, then load/run the plugin in Figma once
(see Install → step 2).
doctor fails on "chrome" — no local Chrome/Chromium found; install
one, or point figma-map.yaml at a binary via the chrome path setting.
doctor fails on "storybook" — nothing is serving index.json on the
configured URL; start Storybook (npm run storybook or equivalent) first.
- A request stalls on a huge document — the plugin heartbeats while it's
still working and the backend's timeout resets on each one, so it won't
get killed just for taking a while; but a full-document styles walk over a
very large node count is still genuinely slow. Scope the call with
--depth instead of walking the whole file (see
docs/architecture.md).
- Still stuck after Figma is in the foreground and reconnected — restart
the backend (
figma-map bridge down && figma-map bridge up --repo <path>)
and re-run the plugin once from Plugins → Development.
Roadmap
Where this is headed next, in rough priority order:
- Deeper agent ↔ issue integration — a tighter, more explicit hand-off
than "the agent polls the inbox": the agent should be able to claim an
issue, report progress back on it, and close the loop without the human
re-explaining context already captured when it was flagged.
- Arbitrary-region diff selection — today a flagged issue is tied to
one element; letting a human drag-select any region of the page (not just
a single node) and hand that to the agent as the unit of comparison.
- Diff-to-fix, not just diff-to-look-at — show the agent the actual
visual diff for a flagged issue (not only the Figma-side tokens), so it can
reason about what changed on screen, not just what the spec says.
- Large-document performance — the backend no longer times out a
large request outright (the plugin heartbeats, the backend's inactivity
window resets on each one), but a full-styles walk over a document with a
very large node count is still genuinely slow. The workaround today is
scoping calls with
--depth (see Troubleshooting);
actually speeding up the walk itself is still open.
- One-click plugin/extension install — publish the Figma plugin to
the Community and the browser extension to the Chrome Web Store, so step
2/3 above stop being a manual "load unpacked."
- Wider
reconcile coverage — margins, box-shadow, and gradient
fills aren't diffed yet.
- Idiomatic boolean props in codegen (
disabled instead of
disabled="true").
- REST-source parity — close the gaps between the live bridge and
the headless/CI-friendly REST backend (bound-variable resolution,
prototyping reactions, dev-resources, annotations).
See docs/limitations.md for the full, current list of
gaps — and CHANGELOG.md for what's already shipped.
Documentation
The above is enough to get productive. For everything else:
Contributing
Contributions are welcome — see CONTRIBUTING.md for the dev
workflow, and CODE_OF_CONDUCT.md for community guidelines.
make build # build the binary
make test # run tests with the race detector
make lint # golangci-lint
License
MIT © Kirill Baranov