$npx -y skills add strongeron/storybook-workbench --skill sb-figmaThe Figma↔Storybook bridge, both directions, via the native Figma MCP. design→code: map foundation tokens (color/spacing/type) from Figma variables with design↔code parity + drift, and deliver approved Figma components (extract → build → embed), authoring stories via sb-stories'
| 1 | # sb-figma — Figma → production Storybook delivery |
| 2 | |
| 3 | `sb-figma` owns the **DELIVER** stage of the Figma→Storybook lifecycle: an **approved** design becomes |
| 4 | production Storybook. It is Figma-aware (uses the Figma MCP), but it is **not** the exploration skill. |
| 5 | |
| 6 | > **The lifecycle line (load this first).** *Undecided / trying options* → **`sb-explore`** (Lab sandbox, |
| 7 | > iterate against a Figma node) → **`sb-ship`** (graduate the Lab experiment). *Already approved in Figma* → |
| 8 | > **`sb-figma`** (deliver direct to prod). Both touch Figma MCP; the difference is **exploration vs delivery**. |
| 9 | > If the user is still deciding, hand off to `sb-explore`. See `references/figma-token-sync.md`. |
| 10 | |
| 11 | ## Job 0 — capture the MCP output (always, before anything else) |
| 12 | |
| 13 | > **Force the NATIVE structured tools — never work from a screenshot.** A screenshot is pixels; it cannot |
| 14 | > give you variables, styles, or component props. The full picture comes ONLY from the native MCP tools, and |
| 15 | > you must pull all three categories before building or connecting: |
| 16 | > - **Variables** → `get_variable_defs` (the token values: color/spacing/type/effect, resolved). |
| 17 | > - **Components + styles** → `get_design_context` (the reference code, applied styles, props/variants). |
| 18 | > - **Structure** → `get_metadata` (the node tree; on truncation, drill to child node-ids — never give up at |
| 19 | > the parent). |
| 20 | > `get_screenshot` is **visual reference only** — for an eyeball diff after you've built from the structured |
| 21 | > data. NEVER read tokens, props, or layout off a screenshot. If a tool returns "nothing selected" on a page |
| 22 | > id, drill to a concrete component node (a page is not a layer). |
| 23 | |
| 24 | Scripts can't call the Figma MCP, and an MCP result lives only in the agent's context — ephemeral, gone |
| 25 | on the next session, absent headless. So **every Figma MCP call you make, persist it** through the universal |
| 26 | store before using it. This is what makes the pipeline reproducible and iterable. |
| 27 | |
| 28 | ```bash |
| 29 | # any JSON-returning tool — pipe its output straight in: |
| 30 | <get_variable_defs output> | node scripts/capture-figma.mjs --tool get_variable_defs --file <FILE> --node <NODE> --from-mcp - |
| 31 | <get_design_context output> | node scripts/capture-figma.mjs --tool get_design_context --file <FILE> --node <NODE> --from-mcp - |
| 32 | <get_metadata output> | node scripts/capture-figma.mjs --tool get_metadata --file <FILE> --node <NODE> --from-mcp - |
| 33 | <get_code_connect_map out> | node scripts/capture-figma.mjs --tool get_code_connect_map --file <FILE> --node <NODE> --from-mcp - |
| 34 | # get_screenshot returns an image — save it, then register the file: |
| 35 | node scripts/capture-figma.mjs --tool get_screenshot --file <FILE> --node <NODE> --image /tmp/frame.png |
| 36 | # see the whole inventory (degrade / iterate): |
| 37 | node scripts/capture-figma.mjs --list |
| 38 | ``` |
| 39 | |
| 40 | Store layout: `.storybook/figma/manifest.json` + `.storybook/figma/<tool>/<node>.json` (images keep their |
| 41 | ext). Re-capturing a (tool,node) overwrites — diff against git to see what moved in Figma. **Downstream steps |
| 42 | read the store, never re-call MCP.** |
| 43 | |
| 44 | ## MCP realities (field-verified against the Detections file, 2026-06-22) |
| 45 | |
| 46 | What the live Figma MCP actually returns — the scripts already handle these; know them so you don't fight the output: |
| 47 | - **`get_variable_defs` is a FLAT `{ "name": "value" }` map** (not nested DTCG, not an array). Colors come **already |
| 48 | resolved to hex** (`"semantic/background":"#fbfcfc"`), incl. 8-digit alpha (`"#e4e5e580"`). Numbers are bare |
| 49 | strings (`"spacing-2":"8"`, `"wght/semibold":"650"`). **Ty |