figma-map

command module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2026 License: MIT Imports: 3 Imported by: 0

README

figma-map

Map Figma design components to your code component library using a vision LLM.

Match once with AI into a reviewable binding, then generate code deterministically.

CI Go Reference Go Report Card Release


figma-map answers a question every design-system team hits: "which <Button> is this Figma layer, and with what props?" — automatically.

Instead of hand-maintaining a mapping between your design library in Figma and your component library in code, figma-map builds that mapping once with a vision LLM, lets you review it, then applies it deterministically to generate JSX.

// 13:1077 → Button (0.80)
import { Button } from "@/components/ui/button"

<Button>View docs</Button>

Contents

Why

Translating a Figma design into code means repeatedly recognizing "this is our Button, in the secondary variant, large size" and writing the matching JSX. That recognition is mechanical but tedious, and it drifts as the design system evolves.

figma-map treats the design library and the code library as two sets of images and learns the correspondence between them — the same way you would by eye, but captured as a committable artifact instead of living in someone's head.

How it works: bind → apply

The tool is built around a reviewable artifact, figma-map.binding.yaml, the same way codegen and i18n tools work. The AI runs once, during bind; everything downstream is deterministic and CI-friendly.

  Storybook ──scan──▶  catalog/            (screenshots + import paths, no AI)
                          │
  Figma ────────────────┐ │
                        ▼ ▼
                  bind  (vision LLM, once)  ──▶  figma-map.binding.yaml
                                                       │  ← you review it
                                                       ▼
  Figma node ──── map (deterministic) ───────────▶  JSX
  1. scan — screenshot every Storybook story into a code-component catalog.
  2. bind (AI, once) — match each Figma component section to the catalog and infer each component's prop schema → write figma-map.binding.yaml.
  3. review the binding — it is a draft; correct anything the model got wrong.
  4. map (cheap, repeatable) — for any Figma node, identify its component and prop values from the binding and emit JSX.

Install

One line — detects your OS/arch, downloads the matching release, verifies its SHA-256 checksum, and installs the binary:

curl -fsSL https://raw.githubusercontent.com/KirillBaranov/figma-map/main/install.sh | sh

Overrides: FIGMA_MAP_VERSION=v0.1.0 to pin a tag, FIGMA_MAP_INSTALL_DIR=~/bin to choose the directory.

With Go:

go install github.com/kirillbaranov/figma-map@latest

Or download a prebuilt archive from the releases page.

Requirements
Dependency Why
Google Chrome / Chromium headless screenshots of Storybook stories
Storybook 7+ running exposes the index.json story manifest
figma-mcp-bridge running connects an open Figma file to a local server on :1994, bypassing Figma API rate limits
OpenAI-compatible vision endpoint + key matching and prop inference (works with OpenAI, a local Ollama/llava server, or any compatible gateway via llm.baseURL)

Quick start

cp figma-map.example.yaml figma-map.yaml      # adjust URLs if needed
export OPENAI_API_KEY=sk-...

figma-map doctor                              # verify bridge, chrome, storybook, key

# 1. Build the code-component catalog (no AI).
#    --project points at the repo containing your *.stories.tsx files.
figma-map scan --project /path/to/storybook-project

# 2. Match Figma to the catalog and write the binding (AI, run once).
figma-map bind
#    → review figma-map.binding.yaml

# 3. Generate code for any Figma node.
figma-map map 13:1077

Commands

Command Description Uses AI
figma-map doctor Check bridge, Chrome, Storybook, and API key
figma-map scan Screenshot Storybook stories → catalog/
figma-map bind Match Figma sections to the catalog + infer prop schemas → figma-map.binding.yaml ✓ once
figma-map list List the components in a binding
figma-map tokens <nodeId> Exact design tokens (color/spacing/font/radius) for a node
figma-map inspect <nodeId> Node subtree: structure, text, bounds, optional --tokens
figma-map screenshot <nodeId> Render a node to PNG (--out to save)
figma-map export-assets <nodeId> Export a node to SVG/PNG/JPG
figma-map map <nodeId> Identify a node's component + props → JSX ✓ cheap
figma-map plan <frameId> Map every instance in a frame → buildable spec ✓ cheap
figma-map reconcile <nodeId> Diff rendered output vs the design (deterministic) — / opt-in
figma-map mcp Run as an MCP server over stdio (for agents)

Pass --file <fileKey> to any command when multiple Figma files are connected, and --json for machine-readable output. Run figma-map <command> --help for full flags.

Agent / MCP integration

figma-map is built to be driven by an AI coding agent — point it at a Figma frame and have it build the page, verifying against the design in a loop. Every command above is also an MCP tool (same names, same parameters): the CLI and the MCP server are generated from one registry, so they never drift.

Configure your agent (Claude Code, Cursor, …):

{ "mcpServers": { "figma-map": { "command": "figma-map", "args": ["mcp"] } } }
The loop: build a page from a mockup

The agent owns the loop; figma-map is a deterministic tool — it measures, it doesn't guess (see ADR-0001).

  1. plan <frameId> → a buildable spec: layout, each component instance mapped to your code (import + props), exact tokens, and an honest list of what couldn't be mapped.

  2. The agent writes the code, stamping each element with data-figma-node="<id>" so it can be measured later. Unmapped pieces are hand-built from tokens; assets come from export-assets (not regenerated).

  3. The agent renders it (a Storybook story or a dev-server URL).

  4. reconcile <frameId> --story <id> (or --url) → figma-map renders the implementation, reads its DOM computed styles, and diffs them against the design's exact tokens, returning per-element is/should numbers:

    { "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" } ] } ] }
    
  5. The agent fixes the exact properties and loops from step 3 until match: true. Add --semantic for an LLM check of missing elements / wrong assets that numbers can't catch.

Because the feedback is exact numbers tied to specific elements, the loop converges — this is what makes an otherwise-unreliable agent reliable.

A ready-made agent skill ships at .claude/skills/figma-map/SKILL.md: it teaches an agent the loop, the data-figma-node contract, and when to use each operation. Claude Code picks it up automatically when figma-map work comes up.

Configuration

See figma-map.example.yaml. The API key is never stored in the file — it is read from the environment variable named by llm.apiKeyEnv (default OPENAI_API_KEY).

bridge: http://localhost:1994
storybook: http://localhost:6007
fileKey: ""            # default file; empty = sole connected file
llm:
  baseURL: ""          # empty = OpenAI; or a gateway / Ollama endpoint
  model: gpt-4o-mini
  apiKeyEnv: OPENAI_API_KEY

Architecture

cmd/                 cobra root + `figma-map mcp`
internal/
  op/                operation registry — one declaration → CLI command + MCP tool
  clibind/           binds an input struct to cobra flags/args (same tags as MCP)
  service/           all logic (deterministic-first; lazy LLM)
  config/            figma-map.yaml + env override
  figma/             Source interface + bridge backend; node tokens (Style)
  storybook/         index.json → catalog; chromedp screenshots; import parsing
  render/            chromedp DOM extraction (computed styles) + screenshots
  matcher/           Matcher interface + vision implementation
  binding/           figma-map.binding.yaml model (load/save)
  codegen/           binding + props → JSX
  llm/               OpenAI-compatible vision client (configurable base URL)

Each operation is declared once in internal/op; the CLI subcommand and the MCP tool are both generated from it, so they cannot drift (enforced by a convergence test). The figma.Source and matcher.Matcher interfaces are extension seams: a Figma REST backend (for CI) and an embedding-based retriever (for large libraries) can be added without touching callers.

Limitations

Honest gaps in the current release, not hidden behaviour:

  • The binding is an AI draft. bind infers prop values from story names using library conventions; it can miss an exact code value or invent a prop. Review the binding — that human-in-the-loop step is the design, not a bug.
  • Boolean props are stringified (disabled: ["false", "true"]) and rendered as disabled="true" rather than the idiomatic bare disabled. Planned.
  • Import paths come from the story source as written; relative imports stay relative. Adjust in the binding or normalize to your alias.
  • Static screenshots only — hover/focus/active states are not observable, so variants differing only by interaction state cannot be distinguished.
  • reconcile alignment — design nodes are matched to DOM elements exactly via data-figma-node when present, otherwise by geometry/type/text so it works on an existing, untagged implementation (matched-by-position results are flagged lower-confidence). Unmatched nodes are reported unmeasured, never assumed correct. The goal is spec-perfect (every measured property matches the design), not pixel-raster identity, which font rendering makes unattainable.
  • reconcile property coverage: color/background, font size/weight, line-height, letter-spacing, text-align, border radius/width/color, padding, gap, opacity, and element width/height. Not yet checked: margins, box-shadow, and gradient fills. Width/height can be content-driven, so treat those diffs as advisory.
  • Responsive is per-frame — reconcile checks against one frame at the frame's width; behavior between breakpoints the design doesn't specify is out of scope.
  • The bridge requires Figma desktop open with the plugin running. A REST backend for headless/CI use is a planned figma.Source implementation.

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

Documentation

Overview

Command figma-map maps Figma design components to code components using a vision LLM, producing a reviewable binding and deterministic codegen.

Directories

Path Synopsis
Package cmd wires the figma-map CLI.
Package cmd wires the figma-map CLI.
internal
binding
Package binding models the persistent figma-map.binding.yaml artifact that maps a Figma component library to a code component library.
Package binding models the persistent figma-map.binding.yaml artifact that maps a Figma component library to a code component library.
clibind
Package clibind binds a typed input struct to cobra flags and positional arguments using struct tags, so a single struct defines parameters for both the CLI and the MCP tool (whose JSON schema the MCP SDK derives from the same struct).
Package clibind binds a typed input struct to cobra flags and positional arguments using struct tags, so a single struct defines parameters for both the CLI and the MCP tool (whose JSON schema the MCP SDK derives from the same struct).
codegen
Package codegen renders a JSX snippet from a matched component binding and a set of inferred prop values.
Package codegen renders a JSX snippet from a matched component binding and a set of inferred prop values.
config
Package config loads figma-map configuration from a YAML file with environment-variable overrides.
Package config loads figma-map configuration from a YAML file with environment-variable overrides.
figma
Package figma defines the domain model for a Figma document and the Source interface that abstracts where that data comes from.
Package figma defines the domain model for a Figma document and the Source interface that abstracts where that data comes from.
llm
Package llm wraps an OpenAI-compatible chat client for vision calls that return structured JSON.
Package llm wraps an OpenAI-compatible chat client for vision calls that return structured JSON.
matcher
Package matcher decides which code component a Figma image corresponds to.
Package matcher decides which code component a Figma image corresponds to.
op
Package op is the convergence layer: each operation is declared once as an Op[In,Out], and both the CLI subcommand and the MCP tool are generated from it.
Package op is the convergence layer: each operation is declared once as an Op[In,Out], and both the CLI subcommand and the MCP tool are generated from it.
render
Package render drives headless Chrome to render the agent's output and read the real, computed values from the DOM.
Package render drives headless Chrome to render the agent's output and read the real, computed values from the DOM.
service
Package service holds all of figma-map's logic behind one type.
Package service holds all of figma-map's logic behind one type.
storybook
Package storybook builds a catalog of code components by reading a running Storybook's index.json, screenshotting each story, and parsing the real import statement from the story source file.
Package storybook builds a catalog of code components by reading a running Storybook's index.json, screenshotting each story, and parsing the real import statement from the story source file.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL