fig2r — Figma Design Implementation
fig2r is a Rust CLI that turns a Figma URL into a pixel-perfect React+Tailwind reference component in a temp directory. The reference is a design spec, not production code. The agent reads the spec, then implements the design in the target project using that project's own components, tokens, and conventions.
The Iron Law
The reference goes in /tmp. It never lands in the project tree as-is.
The project's code style, component library, design tokens, icon library, and font pipeline all differ from fig2r's generic output. Copy-pasting the reference ships class-soup that conflicts with everything else in the repo.
Specifically, never:
- Copy
/tmp/fig2r-output/ComponentName/ComponentName.tsxintosrc/ - Keep raw hex values (
bg-[#3A422C]) when the project has a matching token - Ship
<img src="/assets/check.svg">when the project already haslucide-reactinstalled - Import from fig2r's generated
icons/folder when standard icons exist in the project's icon library - Recreate
<Button>/<Card>/<Avatar>primitives the project already has incomponents/ui/ - Use literal font family names when the project uses
next/font - Emit
h-full/self-stretch/min-h-0on text leaves inside fixed-height parents (clips text)
Workflow
1. Fetch
fig2r fetch "<figma-url>" --save /tmp/fig2r-output
Output:
/tmp/fig2r-output/
ComponentName/ComponentName.tsx # the reference spec
ComponentName/index.ts # re-export
assets/ # raster images, custom SVGs
icons/ # SVG React components
One-time setup:
npm install -g fig2r
fig2r auth "figd_..." # token from Figma → Settings → Security
# scope: file_content:read
Alt: export FIGMA_TOKEN=.... Set NO_UPDATE_NOTIFIER=1 in CI.
If fig2r is not on PATH (this skill is present but the CLI was never
installed globally — e.g. command -v fig2r returns nothing): use npx.
It works identically without a global install:
npx fig2r@latest fetch "<figma-url>" --save /tmp/fig2r-output
Every CLI command in this skill works the same way under npx — just prefix
with npx fig2r@latest instead of fig2r. Pin @latest so npx always
pulls current fixes. Still need FIGMA_TOKEN set (env var or ~/.fig2r/config.toml).
Suggest the user run npm install -g fig2r if they'll use it more than once —
global install is faster per-run and auto-links this skill.
Always run the latest version. fig2r ships bug fixes and Figma-API
fidelity improvements regularly. On every fig2r fetch the CLI checks
npm once per 24h and prints to stderr when newer:
[fig2r] Update available: 0.1.1 → 0.2.0
Run: npm install -g fig2r@latest
If that banner appears, update before continuing:
npm install -g fig2r@latest
Then re-run the fetch. The older version may have bugs already fixed
upstream — running it wastes the round-trip. Silence the banner in CI
with NO_UPDATE_NOTIFIER=1 (don't silence it locally).
2. Read the reference
Read /tmp/fig2r-output/ComponentName/ComponentName.tsx. It encodes:
exact layout, colors, typography, sizing, borders, effects, positioning.
Treat every value as truth until a matching project token is found.
Do not copy this file into the project. Read it to understand the design.
3. Survey the project
Before writing any code, detect the stack. See references/project-adaptation.md §1 for exact commands. At minimum, identify:
- Component library (shadcn / Radix / MUI / Chakra / Mantine / custom)
- Styling (Tailwind v3 / v4 / CSS Modules / styled-components / emotion)
- Icon library (lucide-react / heroicons / phosphor / react-icons)
cn()utility import path- Path alias (
@/,~/, relative) - Design tokens (Tailwind
theme.extend/ v4@theme/ CSS variables) - Font setup (next/font with
variable:/ Google Fonts / @font-face)
Skipping this step = generic output that doesn't match the codebase.
4. Adapt and implement
Using the reference as the spec, edit files in the target project:
Reuse existing components — if the project has
<Button>, use it. Match variant by comparing colors/padding/border against its variant definition.Map colors to tokens — replace every
bg-[#xxx]with the matching token (bg-primary,bg-brand-default,bg-accent). Raw hex is a fallback, not a default.Map spacing/radii/font-size to tokens —
rounded-[8px]→rounded-lgif the project defineslgas 8px. Same forp-*,text-*,gap-*.Replace icon SVGs with library components —
<img src="/assets/check.svg">→<Check className="w-5 h-5" />. Only keep SVG files for custom illustrations with no library match.Match fonts via the project's pipeline — with next/font, use
font-[var(--font-xxx)], never literal family names.Match the exact design — don't round
21pxto20pxto "feel cleaner". Pixel-perfect means pixel-perfect.Follow project patterns — forwardRef, CVA variants, props interfaces, file layout, export style.
Deduplicate assets before copying — fig2r writes every image to
/tmp/fig2r-output/assets/. Before copying intopublic/assets/, check if the same asset already exists in the project:# Hash-compare: same bytes under a different name for f in /tmp/fig2r-output/assets/*; do h=$(shasum -a 256 "$f" | cut -d' ' -f1) match=$(find public -type f -exec shasum -a 256 {} + 2>/dev/null | grep "^$h " | head -1) [ -n "$match" ] && echo "DUPE: $f -> ${match##* }" doneWhen a match exists, reference the existing path; do not copy the new file. Same-name-different-bytes means the design changed — rename (e.g.,
hero@v2.png) rather than overwriting.Handle failed downloads — check stderr for
[SUMMARY] failed_downloads=N.N > 0means some assets referenced in the.tsxwere NOT written — shipping as-is gives broken<img>at runtime. Options in order of preference:- Re-run
fig2r fetch— most failures are transient (5xx from Figma S3). - Use the Figma MCP — if installed (tool
mcp__plugin_figma_figma__get_screenshot/get_design_context), fetch the missing image directly from the Figma node. Save into the project's public dir. - Use any other image-fetch tool available (web fetch on the Figma
image URL in
asset.url, screenshot tools, etc.). - Ask the user for the missing asset if automated recovery fails.
Never leave the component referencing a file that isn't on disk.
- Re-run
Add interactivity — fig2r output is static. Wire onClick, state, forms, routing, data fetching using the project's existing patterns.
Full mapping tables (shadcn/Radix/MUI/Chakra/Mantine, icons, tokens, fonts, Tailwind v4, variants, dark mode) in references/project-adaptation.md.
5. Verify before declaring done
- Every color class is a token (or justified hex)
- Every icon uses the project's icon library (or justified custom SVG)
- Every size/radius/spacing uses a token where one exists
- No primitives were recreated — existing components are used
- Font families route through the project's font pipeline
- Assets live under the project's public dir, not
/tmp failed_downloads=0in the fetch summary (or every missing asset was recovered via MCP / re-fetch / user)- No duplicate asset copied — existing project assets reused by path
- Component compiles (
tsc --noEmitor equivalent) - Static analysis passes (eslint / biome / whatever the project uses)
Red flags — STOP
Any of these means you're about to violate the Iron Law:
- "I'll just copy the file over and tweak it" — NO. Read spec, rewrite.
- "The raw hex is fine for now, I'll refactor later" — NO. Map tokens on first pass; "later" doesn't come.
- "Fig2r's class list is longer than the project's, I'll trust it" — NO. Long class lists are an artifact of generic output; map to tokens.
- "I'll use the SVG file instead of looking up the icon" — NO. Five seconds
to check the icon library saves a
public/assets/bloat. - "The project has
<Button>but fig2r's div looks different, I'll keep the div" — NO. Match variants. If no variant matches, add a new one to<Button>; don't bypass it. - "I'll skip the project survey, this one's obvious" — NO. Run the detection commands.
- "Download failed but I'll leave the
<img>tag — user will notice the 404" — NO. Re-run, use Figma MCP, or ask. Never ship broken asset refs. - "I'll just copy all assets from /tmp into public/" — NO. Dedupe first. Existing project assets win.
- "Update banner printed but the current version works" — NO. Update first, re-run. Bugs in the old version may silently corrupt output.
Rationalization → reality
| Thought | Reality |
|---|---|
| "Reference is close enough to production" | Reference is tokens-naive, library-naive, pattern-naive. Always adapt. |
| "The project is small, tokens don't matter" | Tokens compound; starting raw-hex makes every future design drift. |
| "I don't know the project well enough to adapt" | Run the survey commands in references/project-adaptation.md §1. Takes <2 min. |
| "Library component doesn't match the design exactly" | Edit the variant, or add a new one. Don't bypass the component. |
| "Icon lookup is tedious" | Fig2r emits filenames that match library names ~90% of the time. |
| "Agent can't tell if a hex matches a token" | Grep the config for the hex. If within 2-3 units, use the token. |
| "The user didn't ask for token mapping" | Token mapping is table stakes for production React code. |
| "failed_downloads warning is informational" | It means the .tsx points at files that don't exist. Always recover. |
| "Copying all assets is safer than deduping" | Safer = larger bundle, duplicate S3 bytes, and stale references when design updates. Dedupe. |
| "Update banner is informational" | It means a bug you'd hit may already be fixed. Update first, always. |
CLI reference
fig2r fetch "<url>" --save <dir> # fetch + generate
fig2r fetch "<url>" # IR JSON → stdout (pipe to convert)
# Useful flags on fetch:
--public-dir ./public # assets → ./public/assets/
--icon-library lucide # import hints (none|lucide|phosphor|heroicons|react-icons)
--responsive # root: w-full max-w-[Npx]
--naming kebab # kebab-case files (default: pascal)
--svg-mode file # svg handling (react-component|file|inline)
--no-theme # skip theme token extraction
--no-index # skip index.ts re-exports
--flat # no per-component subdirectories
--cn-import "@/lib/utils" # cn() import path
--quiet # suppress non-error logs
fig2r auth "<token>" # saves ~/.fig2r/config.toml
fig2r convert <ir.json> -o <dir> # convert pre-built IR
fig2r convert <ir.json> -o <dir> --strict # fail on ambiguous nodes (CI)
fig2r validate <ir.json> # well-formedness
--strict is on convert, not fetch. For CI gating:
fig2r fetch <url> | fig2r convert -o <dir> --strict.
Common patterns
# New component — use --icon-library + --cn-import matching project
fig2r fetch "<url>" --save /tmp/fig2r-output \
--icon-library lucide --cn-import "@/lib/utils"
# Next.js — route assets into public/
fig2r fetch "<url>" --save /tmp/fig2r-output --public-dir ./public
# Multiple sections of a page — fetch each node separately
fig2r fetch "<hero-url>" --save /tmp/fig2r-hero
fig2r fetch "<footer-url>" --save /tmp/fig2r-footer
# adapt + compose in page.tsx
# Updating existing component — fetch, diff visually, edit in place
fig2r fetch "<new-design-url>" --save /tmp/fig2r-output
# preserve existing logic, state, handlers, tests; update only classes/structure
What fig2r handles
Layout (flex, gap, padding, alignment, overflow, wrap), sizing (fixed px, flex-1, min/max, shrink-0), absolute positioning, rotation, colors (hex, hex+alpha, linear/radial gradients), typography (size, weight, family, line-height, letter-spacing, alignment, italic, truncation, max-lines), borders (width incl. fractional, per-side, color, dashed, per-corner radius), effects (drop-shadow, inner-shadow, blur, backdrop-blur), opacity (node and text level), images (PNG @2x, SVG vectors), icons (vector frames as single SVG), semantic HTML (button/header/nav/footer/section/aside/hr from layer names), component variants (props interface from Figma definitions).
Troubleshooting
- Auth error —
fig2r auth "<token>"orFIGMA_TOKEN=.... Scope:file_content:read. - Unsupported platform —
npm install -g fig2rto re-resolve optional platform deps. - Update notice —
npm install -g fig2r@latest. Silence withNO_UPDATE_NOTIFIER=1orCI=1. - Ambiguous output — pipe through
fig2r convert --strictto surface constructs that were silently coerced.
Senior-dev tips
Short hits — details in references/advanced-workflows.md:
- Fetch per component, not per page — smaller, diffable, reusable.
Ask designer to select the component and copy link (URL gets a
?node-id=suffix). - Design update? Re-fetch to a
-v2dir and diff — preserve handlers, state, tests, props, i18n bindings; apply only the deltas. - Responsive — fetch mobile + desktop frames separately, merge with
md:/lg:Tailwind prefixes (mobile-first) or@containerqueries. - Pull Figma variables via MCP (
get_variable_defs) — maps hex → exact token name, no guessing. - State variants — fetch each (hover/disabled/loading/empty/error) or wire props via CVA. Reference shows happy path only.
- Static content → props — no literal "John Doe" / placeholder URLs in production. Extract to props or i18n keys.
- Dark mode — use theme-responsive tokens (
bg-background), notdark:bg-[#...]for every hex. - Anchor comment —
// figma: <file-key>/<node-id>at the top of the component so future agents can re-fetch. - Lock it in — write a Storybook story; add a visual-regression snapshot (Playwright / Chromatic).
- CI drift detection — check in IR JSON fixtures; re-fetch + diff in
CI; fail with
fig2r convert --stricton unsupported constructs. - Productionize images —
<Image>(next/image) over<img>; SVGO the custom SVGs;priorityonly on LCP. - A11y beyond tags — contrast, focus rings, keyboard order, reduced
motion, decorative
aria-hidden. - Don't use fig2r for — charts, complex animations, canvas/WebGL, rich-text editors, video/audio players, non-React targets.
References
- references/project-adaptation.md — stack detection commands; component-library mapping (shadcn / Radix / MUI / Chakra / Mantine); icon mapping; token mapping (Tailwind v3/v4, CSS vars); fonts; variants; a11y; asset dedupe + failed-download recovery; dark mode
- references/advanced-workflows.md — URL hygiene; incremental re-fetch / design updates; responsive multi-breakpoint; state variants; Figma variables via MCP; storybook + visual regression; CI drift detection; perf; i18n; when not to use fig2r
- references/ir-schema.md — IR JSON schema for
the
convertfallback path
Source: taradepan/fig2r — distributed by TomeVault.