---
name: figma-to-component
description: Read designs from Figma and generate React/Next.js App Router/Vue/Angular components following project conventions. Supports specific frame URLs and entire pages.
keywords: figma, design, component, ui, generate, react, nextjs, nextjs-app-router, vue, angular, frontend
---

# Figma to Component

## When to use

- Received a Figma link from a designer and need to generate a component
- Designer has approved the UI, developer starts implementation
- Need to ensure pixel-perfect matching with the design

---

## Gate mode (called from Gate 3 of the gate workflow)

When invoked by Gate 3 and `plan/[ticket-id]/design/nodes.json` exists:

- **Skip Step 0, Step 1, Step 2, Step 2.5** — the design was already read and images already
  exported during Gate 1. Do NOT call Figma MCP again.
- Load the cached design from `plan/[ticket-id]/design/nodes.json` and the image mapping from
  `plan/[ticket-id]/design/design-context.md` (section "Image Map").
- **Copy images** from `plan/[ticket-id]/design/images/` to `public/assets/figma/` before
  generating components, so the `src` paths in the generated code resolve.
- Continue from **Step 3** (detect CSS tooling) onward.

When invoked standalone (no `design/nodes.json`): run the full Process from Step 0.

---

## Step 0: Verify Figma MCP is available

Before doing anything, confirm the Figma MCP server is connected in this session:

- The tool `mcp__figma__get_figma_data` (and `mcp__figma__download_figma_images`) must be available.
- If it is **not** available, STOP and tell the developer:
  > Figma MCP not connected. Run `aiflow init -a figma` (REST API token) or
  > `aiflow init -a figma-desktop` (Figma Desktop), then restart the session.

Do not guess design values from the URL — without the MCP server the skill cannot read the design.

---

## Process

### Step 1: Identify input

Receive one of the following from the user:
- Figma URL frame: `https://www.figma.com/design/<fileKey>/...?node-id=<nodeId>`
- Figma URL file: `https://www.figma.com/design/<fileKey>/...`
- Node description: "UserCard component in file ABC"

Extract `fileKey` and `nodeId` from the URL.

### Step 2: Read design via Figma MCP

Use Figma MCP tools to fetch design information:

```
1. Get node info, styles, and components in one call:
   get_figma_data(fileKey, nodeId, depth=2)

2. Export image if visual reference needed:
   download_figma_images(fileKey, nodes=[{ nodeId, fileName }], localPath)
```

Note: `get_figma_data` returns layout, fills, strokes, typography, and component
definitions in a single response. No separate style or component fetch is needed.

**Tier 1 call budget (MANDATORY).** Since 2025-11-17 Figma rate-limits by endpoint tier:
`get_figma_data` (file/nodes) and image *renders* share the same **Tier 1** bucket —
only **10-20 calls/min** on a Dev/Full seat, and **~6 calls/MONTH** on a View/Collab seat.
Every exploratory call burns the same budget as an image export, so:

- **ALWAYS pass `nodeId`** — never fetch the whole file.
- Start with the lowest `depth` that can answer the question; do NOT probe depth 1→2→3.
- Max **2 `get_figma_data` calls per design**. If the tree is truncated, make ONE targeted
  follow-up on the specific child nodeId — not a re-read of the parent.
- Keep total Tier 1 calls (metadata + renders) **≤ 8 per minute**; space calls 5-10s apart.
- **Check the cache BEFORE any MCP call** — Gate mode reads `plan/[ticket-id]/design/nodes.json`;
  standalone: if a `plan/*/design/nodes.json` exists for the same fileKey+nodeId, reuse it.

Analyze the output to extract:
- **Layout**: flexbox direction, gap, padding, alignment
- **Sizing**: width/height (fixed vs fill vs hug)
- **Colors**: fills, strokes → map to project CSS tokens / Tailwind colors if available
- **Typography**: font-size, font-weight, line-height, letter-spacing
- **Spacing**: padding, margin, gap
- **Border**: radius, width, color
- **Shadow**: box-shadow
- **States**: hover, focus, disabled, active (if variants present)
- **Responsive**: breakpoints if multiple frames present

### Step 2.5: Image Detection & Export

After receiving node data from Step 2, scan the entire node tree to detect image nodes:

**Detect image nodes:**
- Node has a `fills` array containing at least 1 fill with `type === "IMAGE"` → this is an image node
- Node is a frame/group containing many complex layers (icon, illustration, banner) that cannot be recreated with CSS → needs image export
- Node has `type === "VECTOR"` or `type === "BOOLEAN_OPERATION"` → always export as SVG/PNG

**Naming convention (single source of truth):**

Every exported/provided image is keyed to its node through a manifest so matching never
relies on guessing layer names (which repeat in real designs):

- **File name** — the manifest below is authoritative, so any stable name works. Two accepted forms:
  - `<imageRef>.png` — the full 40-char imageRef hash (e.g. `a1f7af10c49119532fa5e95ccceef6adcac9e86c.png`).
    This is what `download_figma_images` and manual API exports produce by default — **keep it,
    no renaming needed**. Collision-proof. Recommended for manual/bulk download.
  - `<layer-slug>-<imageRef first 8 chars>.png` — human-readable variant (e.g. `top-phase1-554b0901.png`).
    Use when you want readable asset names; you must rename after download.
  - Vector / rendered nodes have no imageRef → `<layer-slug>-<nodeId>.png` (or `<nodeId>.png`).
  - Pick ONE form per project and keep the manifest consistent with the files on disk.
- **Manifest file — one PER DESIGN, not one global file.** Maps **nodeId → file**; several
  nodeIds may map to the same file (reused image). This is authoritative. Location:
  - **Gate mode (a ticket):** `plan/[ticket-id]/design/figma-manifest.json` (next to
    `design-context.md` / `nodes.json`).
  - **Standalone (no ticket):** `public/assets/figma/figma-manifest.<fileKey>.json`.
  - Do NOT share one `figma-manifest.json` across multiple tickets: a `nodeId` is only unique
    within one Figma file, so a global manifest mixes designs and maps the wrong image. The
    **images** stay shared in `public/assets/figma/` (dedup by `imageRef` keeps them unique);
    only the per-design manifest is scoped.

```json
// plan/PROJ-42/design/figma-manifest.json
{
  "11362:27220": "top-phase1-554b0901.png",
  "11362:27219": "livebg-1b-red-fc490ecb.png",
  "I11362:27226;3523:5331;1668:8479": "light-6c5e42a8.png"
}
```

**Mode: DEV-provided images (manual download).** When the DEV exports images themselves
and drops them in `public/assets/figma/`:

1. The DEV names each file per the pattern above (use the download list this skill prints —
   see Step 2.5 output — which gives the exact filename for each node).
2. Ensure the design's manifest (per-design path above) maps each relevant nodeId to its file.
   If the manifest is missing, build it by matching `<layer-slug>` (+ `imageRef`/`nodeId`) to
   the files on disk.
3. **Match order for every image node:** (a) manifest entry → (b) a file on disk matching the
   convention → (c) only if neither, download via MCP. Never re-download what is already there.
4. If a node has no manifest entry and no matching file → list it for the DEV (do NOT fake it).

**0. Reference render (always, first):** render the target frame itself to
`public/assets/figma/_reference-<nodeId>.png` via `download_figma_images` (pass the
frame's own `nodeId`). This is the visual ground truth — keep it open and compare the
generated UI against it before finishing. Never reproduce a layout from node data alone.

**Export images — the anti-429 strategy (standard, follow exactly):**

> **What triggers 429 is the number of Figma API CALLS, not the number of images.**
> Downloading the image *bytes* from the returned AWS S3 URLs does NOT count against the
> Figma rate limit. So: make as FEW Figma calls as possible (ideally 2 total), each
> requesting MANY images, then pull all the bytes from S3. Never loop one-call-per-image.
>
> Rate limits are **tiered per seat** (since 2025-11-17): the render endpoint is Tier 1
> (10-20/min on Dev/Full seats, ~6/MONTH on View/Collab seats) and shares its bucket with
> `get_figma_data`. The fills map is Tier 2 (cheaper) — always prefer it.

Figma exposes images through two endpoints — use both, each in a single batched call:

| Source | Endpoint / MCP | Cost |
|--------|----------------|------|
| **Image fills** (raster fills, `fills[].type==="IMAGE"`) | `GET /v1/files/:key/images` → returns ALL `imageRef → S3 url` in **one** call | **Tier 2** — cheap, rarely 429 |
| **Render-only** (VECTOR/BOOLEAN, or image-fill refs missing from the fills map) | `GET /v1/images/:key?ids=<id1,id2,…all…>` → **one** call for ALL ids at once | **Tier 1** — shared bucket with `get_figma_data`; one batched call is fine |

Steps:

1. **Collect & dedupe by `imageRef`.** The same `imageRef` on many nodes = the SAME picture —
   resolve it once. (Typically cuts 140 nodes → a few dozen unique images.) Name files by ref
   so reuse and re-runs are stable: image fill → `<imageRef>.png` (or `<layer-slug>-<ref8>.png`
   for readability); render-only node → `<layer-slug>-<nodeId>.png`.
2. **Skip what already exists** in `public/assets/figma/` — reuse it, don't re-download. Only
   fetch the missing set. Re-runs become incremental and nearly free.
3. **One call for fills:** hit the fills map once → map of `imageRef → S3 url`. Download the
   missing ones' bytes from S3.
4. **One call for the rest:** VECTOR/render nodes are gathered into a **single** render call:
   `…/images/:key?ids=` with ALL their nodeIds comma-joined. Download the returned S3 URLs.
   (Via MCP: one `download_figma_images` call with all the nodes in its `nodes` array — not one
   call per node.)

   **How one MCP call maps to REST** (verified against `figma-developer-mcp` v0.13.2 source —
   `downloadImages()` makes at most 3 REST calls no matter how many nodes you pass):
   - nodes with `imageRef`/`gifRef` → **1×** fills-map call (Tier 2; skipped if none)
   - nodes with `nodeId` + `.png` filename → **1×** render call, all ids joined (Tier 1)
   - nodes with `nodeId` + `.svg` filename → **1×** SEPARATE render call (Tier 1)
   So: (a) pass EVERYTHING in one MCP call; (b) prefer ONE format for render-only nodes
   (default `.png`) — mixing `.svg` + `.png` filenames doubles the Tier 1 render calls;
   (c) the tool dedupes identical `imageRef`s within a call, but it does NOT skip files
   already on disk and does NOT retry — skip-existing and 429 policy are YOUR job.

4b. **Refs missing from the fills map are dropped SILENTLY.** If a node's `imageRef` isn't in
   the fills map, the MCP returns no file for it — no error. After the call, diff the files on
   disk against what you requested; re-request the missing ones in ONE follow-up call as render
   nodes (`nodeId` only, no `imageRef`). Only nodes still missing after that go to the DEV.
5. **On `429 Rate limit exceeded`:** read the `Retry-After` header first — it tells you which
   limit you hit:
   - **`Retry-After` ≤ 60s** → per-minute leaky bucket. Wait exactly that many seconds, then
     retry **once**. If the retry also 429s, stop and report (below). Never retry more than once.
   - **`Retry-After` > 60s (or absent), or `X-Figma-Rate-Limit-Type: low`** → seat/plan quota
     (a View/Collab seat gets only ~6 Tier 1 calls per MONTH). Do NOT retry. **STOP and report
     to the DEV:**
     > ⚠️ Figma 429 while exporting images (got X/Y). retry-after ≈ <n>. This is the per-seat
     > quota — a new token on the SAME account won't help. Options: use a token from a Dev/Full
     > seat, switch to `aiflow init -a figma-desktop` (no REST quota), or export manually.
     > Missing refs/nodeIds: …
6. **A node may render to `null`** (deeply-nested instance Figma can't render standalone) — this
   is NOT a rate-limit error. Report that one ref for manual export; don't fake it.
7. **Prioritise** if large: backgrounds / hero / full-bleed first, then logos/icons, then art.

- **Verify on disk:** list `public/assets/figma/` and confirm a file exists for every unique
  `imageRef`/`nodeId`. Re-running is cheap (existing files skipped).

**Large / art-heavy design (image nodes > 30):** tell the DEV up front
(`This frame has N image assets; exporting may take several batched calls`). Do NOT skip
the export to save time — the real images ARE the design. If the volume is impractical
in one pass, export backgrounds + key assets, then list the remaining nodeIds for the DEV
instead of approximating them.

**FAIL LOUD — never approximate.** If `download_figma_images` is unavailable, or images
fail to export (e.g. 429), **STOP**. Report which refs/nodes could not be exported and ask
the DEV how to proceed. Do NOT substitute CSS gradients / solid colours for an image node
and present the result as done — that is the #1 cause of "doesn't match the design".

- Fallback when the MCP tool itself is missing: get image URLs via `get_figma_data`,
  give the DEV the URL→filename list, and pause until the files are placed in
  `public/assets/figma/`.

**Record the result:**

After export, persist the mapping to the per-design manifest (`plan/[ticket-id]/design/figma-manifest.json`
in Gate mode, else `public/assets/figma/figma-manifest.<fileKey>.json`) and load it as
`imageMap` for Step 4. Key by `nodeId`; **several nodeIds may point to the same file** when
they share an `imageRef` (reused image — downloaded once). This manifest is the same one the
DEV-provided-images mode reads, so manual and automatic exports converge on one source of truth:

```
imageMap = {
  "123-456": "public/assets/figma/banner-top-554b0901.png",
  "777-888": "public/assets/figma/banner-top-554b0901.png",  // same imageRef → same file, NOT re-downloaded
  "789-012": "public/assets/figma/icon-star-9731a243.png"
}
```

### Step 3: Detect CSS tooling, then map Figma tokens

**Detect the project's styling approach first** (do not assume Tailwind):

1. `tailwind.config.*` present → **Tailwind** — map to utility classes; extend `tailwind.config` with design tokens when a value has no built-in match
2. `*.module.css` / `*.module.scss` files present → **CSS Modules** — emit a co-located `.module.css` and reference `styles.x`
3. `styled-components` / `@emotion` in `package.json` → **CSS-in-JS** — emit styled components
4. None of the above → **vanilla CSS** — emit a co-located `.css` file with CSS custom properties

**Design tokens:** prefer mapping Figma Styles/Variables to existing project tokens
(Tailwind theme keys or CSS custom properties) instead of hardcoding hex/px values.
Only fall back to an arbitrary value when no token matches.

**Tailwind reference mappings** (when Tailwind is the detected tooling):

```
Color:   #3B82F6 → blue-500 · #EF4444 → red-500 · rgba(0,0,0,0.5) → black/50
         no match → arbitrary value [#hexcode]
Spacing: 4→p-1/gap-1 · 8→p-2 · 12→p-3 · 16→p-4 · 24→p-6 · 32→p-8 (8px grid)
         no match → arbitrary value [20px]
Type:    12→text-xs · 14→text-sm · 16→text-base · 18→text-lg · 20→text-xl · 24→text-2xl · 30→text-3xl
         400→font-normal · 500→font-medium · 600→font-semibold · 700→font-bold
```

### Step 3.7: Build faithfully from the node tree (MANDATORY — do not summarize)

The #1 cause of "doesn't match the design" is generating a *re-interpretation* (a few labels +
a hand-picked subset of images in a generic centered/flow layout) instead of reproducing the
**actual node tree**. Follow these rules — they are not optional:

1. **Reconstruct region by region from real child nodes.** Identify the design's regions from
   the top-level containers (e.g. header frame, the form/panel frame, content sections, footer
   frame). For EACH region, build its component from its OWN child nodes — every image, text,
   and button that lives under that region node — placed in the same arrangement the node tree
   has. Do NOT invent a structure or move elements between regions. "Header has its logo + nav +
   buttons; the panel has its inputs + buttons; the footer has its logos + links" — keep each
   asset in the region Figma puts it in.

2. **Pick the background by PAINT ORDER, never by guessing.** Children are painted in array
   order — a LATER sibling paints ON TOP of an earlier one. The visible full-bleed background is
   the last opaque full-bleed layer, not the first one you find. Check sibling order + `opacity`
   + `visible` before choosing which background image to render. (Classic bug: rendering an early
   red layer when a later blue layer actually covers it.)

3. **No-fabricate / no-omit.** The set of elements you render MUST equal the set of nodes in the
   frame. Build a quick checklist from the node tree before coding:
   - Every TEXT string in the frame appears in the UI (don't drop labels; don't translate).
   - Every image node (from the manifest) is placed in its region (don't omit backgrounds, logos,
     icons, decorative art).
   - You add NOTHING that isn't in the frame (no buttons/sections from memory or another frame —
     e.g. do not add an "Apple" button if only Google/Facebook exist in THIS node).
   Diff your planned element list against the node tree; reconcile before generating code.

4. **Match each region's container style to the node**, not to a default: background colour/image,
   light vs dark, logo-as-image vs text, button shape (pill/outline/solid), spacing. Read the
   node's `fills`, sizes and positions — don't assume a dark card or a text logo.

For a fixed-size, absolutely-positioned design (game UI, marketing LP), prefer reproducing each
region's internal arrangement closely (relative positions of its children) over forcing a generic
responsive flow; add responsiveness on top without losing the composition.

### Step 4: Generate component

**Framework detection** — check in this order:

1. Read project's `CLAUDE.md` for framework identifier:
   - `nextjs-app-router` → Next.js App Router (Server/Client Components)
   - `reactjs` / `nextjs` → React / Next.js Pages Router `.tsx`
   - `vue-nuxt` → Vue 3 `.vue` with Composition API
   - `angular` → Angular `@Component` class
2. Scan project files if CLAUDE.md is ambiguous:
   - `app/` directory present → `nextjs-app-router`
   - `angular.json` present → `angular`
   - `nuxt.config.*` present → `vue-nuxt`
3. Fallback → `nextjs` (React Pages Router)

**Image node rendering rule:**

Before rendering each node, check `imageMap` from Step 2.5:
- If `nodeId` is in `imageMap` → render using `<img>` (React/Vue) or `<Image>` (Next.js), do NOT use CSS `background-image`
- If not in `imageMap` → render normally using div + styling classes

React/Next.js template for image node:
```tsx
{/* Image node — exported from Figma */}
<img
  src="/assets/figma/<fileName>.png"
  alt="<layer name from Figma>"
  width={<width from Figma>}
  height={<height from Figma>}
  className="<sizing classes if needed>"
/>
```

Next.js with `next/image`:
```tsx
import Image from 'next/image';
<Image
  src="/assets/figma/<fileName>.png"
  alt="<layer name from Figma>"
  width={<width from Figma>}
  height={<height from Figma>}
/>
```

Vue 3 template for image node:
```vue
<img
  src="/assets/figma/<fileName>.png"
  :alt="'<layer name from Figma>'"
  :width="<width from Figma>"
  :height="<height from Figma>"
/>
```

#### React/Next.js component template:

```tsx
interface [ComponentName]Props {
  // Props extracted from Figma variants or dynamic content slots
  className?: string;
}

export const [ComponentName] = ({ className }: [ComponentName]Props) => {
  return (
    <div className={cn(
      // Layout classes
      // Sizing classes
      // Color classes
      // Typography classes (if text node)
      className
    )}>
      {/* Child nodes rendered recursively */}
    </div>
  );
};
```

#### Next.js App Router component template:

Default to **Server Component**. Add `'use client'` only when the design has
interactive states (hover effects, click handlers, form inputs, modals).

```tsx
// Server Component — no interactivity (default)
interface [ComponentName]Props {
  className?: string;
}

export default async function [ComponentName]({ className }: [ComponentName]Props) {
  return (
    <div className={cn(
      // Layout classes
      // Sizing classes
      // Color classes
      className
    )}>
      {/* Child nodes */}
    </div>
  );
}
```

```tsx
// Client Component — has onClick / useState / useEffect
'use client'

import { useState } from 'react'

interface [ComponentName]Props {
  className?: string;
}

export function [ComponentName]({ className }: [ComponentName]Props) {
  return (
    <div className={cn(
      // Layout classes
      // Sizing classes
      // Color classes
      className
    )}>
      {/* Child nodes */}
    </div>
  );
}
```

File path: `app/components/[ComponentName].tsx` or `components/[ComponentName].tsx`

#### Vue 3 component template:

```vue
<script setup lang="ts">
interface Props {
  // Props extracted from Figma variants
  class?: string;
}

const props = withDefaults(defineProps<Props>(), {});
</script>

<template>
  <div :class="cn(
    // Layout classes
    // Sizing classes
    // Color classes
    props.class
  )">
    <!-- Child nodes -->
  </div>
</template>
```

#### Angular component template:

Use Tailwind classes if `tailwind.config.*` exists in the project root.
Otherwise use `styles` array with CSS-in-component.

```typescript
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-[component-name]',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div [class]="hostClasses">
      <!-- Child nodes -->
    </div>
  `,
  styles: []
})
export class [ComponentName]Component {
  @Input() className = '';

  get hostClasses(): string {
    return [
      // Layout classes
      // Sizing classes
      // Color classes
      this.className
    ].filter(Boolean).join(' ');
  }
}
```

### Step 5: Handle interactive states

If Figma has variants (Default, Hover, Disabled, Active):

```tsx
// Map variants into props + conditional classes
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'outline';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
}

const variantClasses = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700',
  secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
  outline: 'border border-gray-300 hover:bg-gray-50',
};
```

### Step 6: Verify and output

**MANDATORY reference comparison (do this before claiming done — not optional):**
Open `public/assets/figma/_reference-<nodeId>.png` (the rendered frame from Step 2.5) and the
generated UI side by side, region by region. For each region (header / panel / sections / footer)
confirm: same background, same logo (image vs text), same buttons, same text, images in the right
place. **List every visible difference**, fix it, and re-compare. Do not finish while a region is
visibly wrong. If you cannot view the reference, say so and ask the DEV to eyeball it — do not
silently declare a match.

After generation, verify:

- [ ] **Reference compare done**: generated UI matches `_reference-<nodeId>.png` region by region; differences fixed
- [ ] **No-fabricate**: nothing rendered that isn't in the node tree (no buttons/sections from memory/other frames)
- [ ] **No-omit**: every TEXT string + every manifest image is present in the right region
- [ ] **Background** is the correct paint-order layer (the one that actually covers the frame)
- [ ] Layout matches Figma (flex direction, alignment, gap)
- [ ] Colors mapping correct (project tokens, or arbitrary values if no match)
- [ ] Typography correct (size, weight, line-height)
- [ ] Spacing correct (padding, margin, gap)
- [ ] Component has `className` prop for external overrides
- [ ] Conditional/mergeable classes use the project's helper (`cn()` for Tailwind)
- [ ] Responsive if Figma has mobile frames
- [ ] Props typed with TypeScript interface
- [ ] Image nodes use `<img>` / `<Image>` (do not use CSS `background-image`)
- [ ] Image files exist in `public/assets/figma/` before submitting code
- [ ] Compared the rendered result against `_reference-<nodeId>.png` — they look alike
- [ ] No image node was approximated with a CSS gradient/colour; any node that could not be exported is reported to the DEV, not faked

---

## Output format

Always output in order:

1. **Design Summary** (brief): layout structure, color palette, typography scale
2. **Component file** with full code
3. **Usage example**:
   ```tsx
   <ComponentName variant="primary" className="mt-4" />
   ```
4. **Notes** if anything cannot be mapped 100% accurately from Figma

---

## Rules

- **Build from the node tree, region by region** — reproduce each region (header/panel/sections/footer) from its real child nodes; do NOT re-interpret into a generic centered/flow layout. (Step 3.7)
- **Background by paint order** — the visible full-bleed background is the LAST opaque layer, not the first; check sibling order + opacity before choosing.
- **No-fabricate / no-omit** — rendered elements must equal the node set: every text + every manifest image placed; nothing added from memory or another frame.
- **Reference compare is mandatory** — compare the result against `_reference-<nodeId>.png` region by region and fix every difference before claiming done.
- **Detect styling tooling first** — do not assume Tailwind; honour CSS Modules / styled-components / vanilla CSS when detected
- **Prefer design tokens** — map Figma Styles/Variables to project tokens; only hardcode a value when no token matches
- **Always use** the project's class-merge helper (`cn()` = clsx + twMerge for Tailwind)
- **Responsive** — if Figma only has 1 breakpoint, default to mobile-first design
- **Accessibility** — add `aria-label`, `role`, `alt` for interactive and image elements
- **Image** — detect image fills in Figma node data, export via `download_figma_images` MCP: **ONE call with ALL nodes** in its `nodes` array (split only if huge — ~50 ids/call, 5-10s apart). On 429 follow the Retry-After policy in Step 2.5 (≤60s → wait + retry once; larger → report to DEV and stop). Save to `public/assets/figma/`. Use `<Image>` (Next.js) or `<img>` (React/Vue) with `src` pointing to the exported file. Do NOT use CSS `background-image` for image nodes.
- **Never fake an image** — if export fails or the MCP image tool is missing, STOP and report the failed nodeIds. Do NOT approximate a raster/art node with CSS gradients or solid colours and call it done. Always render the frame to `_reference-<nodeId>.png` first and compare against it.
- **Icon** — if Figma uses SVG icons, extract SVG or map to lucide-react / heroicons

---

## Troubleshooting

| Symptom | Cause | Fix |
|---------|-------|-----|
| `mcp__figma__get_figma_data` not available | Figma MCP server not connected | Run `aiflow init -a figma` or `-a figma-desktop`, restart session |
| `403 Forbidden` / token rejected | PAT invalid or wrong header | Re-issue a `figd_...` token; `init` sends `X-Figma-Token` (not `Bearer`) |
| Tool call hangs / times out | Large node tree (`depth` too high) | Lower `depth` (e.g. `depth=1`), target a specific `nodeId` not the whole file |
| `404` on node | Wrong `fileKey`/`nodeId` | Re-copy the frame URL; node-id uses `-` in URL but `:` in API |
| Empty fills / no styles | Node is a component instance | Fetch the main component, or increase `depth` |
| `download_figma_images` unsupported | Older MCP preset | Get image URL via `get_figma_data`, download manually to `public/assets/figma/` |
| `429 Rate limit exceeded` on images | Too many Tier 1 CALLS (metadata probes + per-node loops), or token from a View/Collab seat (~6 calls/MONTH) | Batch ALL ids into one call (Step 2.5). Check `Retry-After`: ≤60s → wait + retry once; hours/days or `X-Figma-Rate-Limit-Type: low` → seat quota — use a Dev/Full-seat token or `figma-desktop` adapter; new token on same account won't help |
| UI rendered but no images / wrong look | Export skipped or failed; layout faked with CSS | Render `_reference-<nodeId>.png`, export real image nodes (batched), replace CSS approximations with `<img>`/`<Image>` |

---

## Trigger Example Commands

```
Read Figma and generate component: https://www.figma.com/design/xxxxx/App?node-id=123-456

Generate UserCard component from Figma frame node-id=123-456

Implement UI from this Figma link following my project: [URL]
```
