Skip to content
Back to skills

Sb Figma

ASecurity

The 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' rules. code→design: build Code Connect mappings from components + stories + token parity + usage so Figma Dev Mode shows the real code. Use for 'sync my Figma tokens', 'map Figma variables to my design system', 'deliv...

  • 37 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 2, 2026
ai-agentstypescriptpythongobashreactnoderailsgitapi

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 20 files and shows the line behind each finding

Scanned October 2, 2026

npx -y skills add strongeron/storybook-workbench --skill sb-figma --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sb Figma?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sb Figma
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/strongeron-sb-figma/badge)](https://www.skillsdirectory.com/skills/strongeron-sb-figma)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sb-figma
description: "The 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' rules. code→design: build Code Connect mappings from components + stories + token parity + usage so Figma Dev Mode shows the real code. Use for 'sync my Figma tokens', 'map Figma variables to my design system', 'deliver this approved Figma design', 'check design↔code token parity', 'connect my components to Figma / code connect'. NOT for prototyping/iterating an undecided design (that's sb-explore)."
compatibility: "Requires bash, python3 and Node.js (the .mjs scripts), plus the Figma MCP server (remote https://mcp.figma.com/mcp preferred). Job 1 reads every variable mode with use_figma (edit access) or falls back to get_variable_defs; Code Connect needs an Organization/Enterprise plan; writes need a Full seat. Without MCP the scripts reuse the cached .storybook/figma-variables.json. Reads project-inventory.json (sb-inventory) when present."
allowed-tools: Bash Read Glob Grep Write Edit
license: MIT
metadata:
  author: strongeron
  version: '2.4.0'
  bundle: storybook-workbench
  vendor:
    scripts: [capture-figma.mjs, pull-figma-variables.mjs, css-tokens.mjs, build-token-parity.mjs, build-figma-variables.mjs, build-code-connect.mjs, workbench-settings.mjs, record-figma-delivery.py]
    wrappers: [TokenMatrix, TokensCanvas, FigmaInventory]
    references: [figma-token-sync.md, read-figma-variables.js, write-figma-variables.js, workbench.schema.json]
    templates: [figma-inventory.stories.tsx]
---

# sb-figma — the Figma ↔ Storybook bridge

`sb-figma` moves an **approved** design into production Storybook (Jobs 1–2) and pushes the code's truth
back to Figma (Job 3, and opt-in writes). It uses the Figma MCP, but it is **not** the exploration skill.

## Preconditions — check before the first call

| Job | Needs | Tools |
|---|---|---|
| Read tokens (Job 1, preferred) | edit access to the file; the **remote** server | `use_figma` (read-only script) |
| Read tokens (fallback) | any seat | `get_variable_defs` |
| Deliver a component (Job 2) | any seat | `get_design_context`, `get_metadata`, `download_assets` |
| Code Connect (Job 3) | **Organization or Enterprise** plan, published library components | `get_code_connect_suggestions`, `get_context_for_code_connect`, `send_code_connect_mappings` |
| Write to Figma (opt-in) | **Full** seat for an existing file (any seat can write to drafts); remote server; beta | `use_figma`, `generate_figma_design` |

- Prefer the **remote** server (`https://mcp.figma.com/mcp`). The desktop server has no write, no
  code-to-canvas and no skills.
- Rate limits are per plan and seat (Starter View/Collab seats get a handful of calls a month; Dev/Full seats
  about 200 a day). `whoami`, `add_code_connect_map` and `create_new_file` don't count.
- A call fails with access, permission or rate-limit errors → call `whoami` (plans + seats) and report what's
  missing instead of retrying.
- Figma's own skills (`figma-use`, `figma-design-to-code`, `figma-code-connect`, `figma-generate-design`) come
  with the Figma plugin; without it, read them from the MCP as `skill://figma/<name>/SKILL.md`. Tools that
  require one say so in their description — load it first and pass `skillNames`.

> **The lifecycle line (load this first).** *Undecided / trying options* → **`sb-explore`** (Lab sandbox,
> iterate against a Figma node) → **`sb-ship`** (graduate the Lab experiment). *Already approved in Figma* →
> **`sb-figma`** (deliver direct to prod). Both touch Figma MCP; the difference is **exploration vs delivery**.
> If the user is still deciding, hand off to `sb-explore`. See `references/figma-token-sync.md`.

## Learn from each run — gaps and project rules

Every run starts here and ends here:

1. **Read the open gaps first:** `node scripts/workbench-settings.mjs gaps`. Each one says what a previous run
   couldn't do and how to fix it. Show them to the user before starting new work.
2. **The scripts record gaps as they go** in `.storybook/figma/gaps.json`: `collision` (several Figma variables
   on one token), `weak-match` (named on the last word only), `unmapped-name` (Figma variables with no code
   token), `unsupported-value` (a colour syntax the parity can't read), `unmapped-variant-value` (a Figma
   variant value with no code value). A gap a later run no longer finds is marked resolved.
3. **Record what only you see:** a value with no token you had to ask about (`missing-token`), a tool Figma
   refused (`tool-refused`):
   `node scripts/workbench-settings.mjs add --kind missing-token --detail "icon #116932 has no token" --suggestion "…"`.
4. **Fix a gap with a project rule, not a skill edit.** Rules live in `.storybook/workbench.json` (scaffold:
   `node scripts/workbench-settings.mjs --init`; schema `references/workbench.schema.json`); the project owns
   the file, so skill updates never overwrite it, and command-line flags still win:
   - `css` — where the token CSS lives · `darkSelectors` — what marks the dark theme
   - `nameMap` — `"fg/error": "--color-error-text"`: beats name guessing, settles collisions
   - `ignore` — Figma variables to leave out (`"Size/*"`) · `variantValues` — `"status - success": "success"`
   - `codeConnectLabel` — default Code Connect label
5. **Something the rules can't fix** (an unsupported colour syntax, a wrong result) → `report-issue.sh --gaps`
   drafts a sanitized issue with the open gaps as counts per kind — no names or values.

## Job 0 — capture the MCP output (always, before anything else)

> **Force the NATIVE structured tools — never work from a screenshot.** A screenshot is pixels; it cannot
> give you variables, styles, or component props. The full picture comes ONLY from the native MCP tools, and
> you must pull all three categories before building or connecting:
> - **Variables** → `get_variable_defs` (the token values: color/spacing/type/effect, resolved).
> - **Components + styles** → `get_design_context` (the reference code, applied styles, props/variants).
> - **Structure** → `get_metadata` (the node tree; on truncation, drill to child node-ids — never give up at
>   the parent).
> `get_screenshot` is **visual reference only** — for an eyeball diff after you've built from the structured
> data. NEVER read tokens, props, or layout off a screenshot. If a tool returns "nothing selected" on a page
> id, drill to a concrete component node (a page is not a layer).

Scripts can't call the Figma MCP, and an MCP result lives only in the agent's context — ephemeral, gone
on the next session, absent headless. So **every Figma MCP call you make, persist it** through the universal
store before using it. This is what makes the pipeline reproducible and iterable.

```bash
# pipe each tool's output straight in (JSON, code text and XML are all stored as returned):
<get_variable_defs output>  | node scripts/capture-figma.mjs --tool get_variable_defs   --file <FILE> --node <NODE> --from-mcp -
<get_design_context output> | node scripts/capture-figma.mjs --tool get_design_context  --file <FILE> --node <NODE> --from-mcp -
<get_metadata output>       | node scripts/capture-figma.mjs --tool get_metadata        --file <FILE> --node <NODE> --from-mcp -
<get_code_connect_map out>  | node scripts/capture-figma.mjs --tool get_code_connect_map --file <FILE> --node <NODE> --from-mcp -
# get_screenshot returns an image — save it, then register the file:
node scripts/capture-figma.mjs --tool get_screenshot --file <FILE> --node <NODE> --image /tmp/frame.png
# see the whole inventory (degrade / iterate):
node scripts/capture-figma.mjs --list
```

Store layout: `.storybook/figma/manifest.json` + `.storybook/figma/<tool>/<node>.json` (images keep their
ext). Re-capturing a (tool,node) overwrites — diff against git to see what moved in Figma. **Downstream steps
read the store, never re-call MCP.**

## MCP realities (field-verified 2026-06-22; tool schemas re-checked 2026-10-01)

What the live Figma MCP actually returns — the scripts already handle these; know them so you don't fight the output:
- **`get_variable_defs` is a FLAT `{ "name": "value" }` map** (not nested DTCG, not an array). Colors come **already
  resolved to hex** (`"semantic/background":"#fbfcfc"`), incl. 8-digit alpha (`"#e4e5e580"`). Numbers are bare
  strings (`"spacing-2":"8"`, `"wght/semibold":"650"`). **Typography is an opaque `Font(family: …, size: …)`
  string** — `pull-figma-variables` parses it to `{family,size,weight,lineHeight,…}` (a field may itself be a
  var-name ref like `size/text-lg`). Shadows are `Effect(…)` → the `effect` family. `classify()` buckets by VALUE.
- **`get_metadata` returns XML and TRUNCATES on large frames** (a table view blew past the token limit). When it
  truncates: read the child node ids from the partial XML and `get_design_context`/`get_metadata` the **sub-node**,
  not the parent. Capture each sub-node to the store so you never re-fetch.
- **Node-ids: the URL uses `1-6965`, the API examples use `1:6965`; every tool accepts both.** `capture-figma`
  stores captures under the dash form so they're found either way. Every tool also needs the `fileKey` from
  the URL (`figma.com/design/<fileKey>/…`; for a branch URL use the branch key).
- **`get_design_context` returns code text plus a screenshot by default; `get_metadata` returns XML.** Only
  `get_variable_defs` and the `use_figma` read script return JSON.
- **Persistence + screenshot↔node linking are solved by Job 0** — the store keeps every output with its node-id in
  the manifest, so you don't hand-cross-reference screenshots or re-call MCP (both were real friction before).

## The jobs (all read the Job-0 store)

### Job 1 — foundation tokens → `Foundations/Colors|Tokens|Type` (sb-figma writes these directly)

No other skill maps Figma *variables* to code tokens, so sb-figma owns the foundation parity end to end.

1. **Read the variables — every mode.** Preferred: load Figma's `figma-use` skill (or the
   `skill://figma/figma-use/SKILL.md` resource), then call `use_figma` with the contents of
   `references/read-figma-variables.js` as `code` and `skillNames: "figma-use"`. It only reads: every local
   collection, every mode (Light, Dark, …), aliases resolved. Big system (output is capped near 20 kB): set
   `ONLY` to one collection and call once per collection. Capture the result (Job 0).
   Fallback, when you can't run `use_figma` (no edit access to the file): `get_variable_defs` on the node
   that uses the tokens. It returns only the variables that node uses, in one mode — dark is not compared.
   Library variables the file consumes but doesn't own: `get_libraries`, then `search_design_system` with
   `{"entity":"variable", …}`.
2. **Normalize** — `node scripts/pull-figma-variables.mjs --from-mcp .storybook/figma/<tool>/<NODE>.json --out .storybook/figma-variables.json`.
   It accepts either shape; the default mode fills `color`/`spacing`/`type`, other modes land in `modes`.
   With no `--from-mcp` it reuses the last cache (the headless degrade path).
3. **Build parity** — `node scripts/build-token-parity.mjs --variables .storybook/figma-variables.json --css <token-css-glob> --out .storybook/figma-token-parity.json`.
   The CSS is read per theme: `:root` is the default, `.dark` / `[data-theme=dark]` / dark `@media` blocks
   are the dark theme. Figma's default mode is compared with the default theme, a Figma "Dark" mode with the
   dark theme (`modes.Dark` in the output). OKLCH channel triplets resolve to hex; `semantic/*` maps to the
   project's `--token`, following `var()` alias chains.
4. **Wire** the foundation stories — pass `figmaParity` to `TokenMatrix` (it reads `figma-token-parity.json`)
   to show drift in the color table's issue column (`figma Δ`, `code #X vs figma #Y` on hover). The fields are
   optional: a project with no Figma file renders as before.
5. **Report drift** in the sb-health shape: Figma value ≠ code value (OKLCH→hex tolerance for color, exact
   for spacing/type), per mode; app-only roles (`--popover`, `--ring`, …) are expected, not failures.

### Job 2 — approved Figma component → production (sb-figma delivers; sb-stories authors)

1. **Size the delivery first — chunk a big board.** A multi-artboard feature (a whole flow, a screen with
   many sections) blows past the MCP token budget. Split by artboard or section: deliver, validate and
   record one part, then the next. The Figma Inventory (step 8) unions stories across parts.
2. **Load Figma's design-to-code guidance.** `get_design_context` now requires it: use the
   `/figma-design-to-code` skill (Figma plugin) or read the `skill://figma/figma-design-to-code/SKILL.md`
   MCP resource, and pass `skillNames: "figma-design-to-code"` (`"resource:figma-design-to-code"` when read
   from the resource) on the call. Set `clientFrameworks`/`clientLanguages` (e.g. `react`, `typescript`).
3. **Extract (into the Job 0 store).** One `get_design_context` call returns reference code, asset download
   URLs and a screenshot; capture it. If it comes back as metadata only (too large), fetch the child nodes
   listed in it instead of the parent. `get_metadata` gives the tree when you need structure alone.
4. **Reuse before building.** Code Connect-mapped components come back in the design context as the real
   import — use them. Otherwise grep for an existing component covering the same concept and extend it
   (guardrails §8 step 3); `search_design_system` finds library components by name.
5. **Assets.** Use an exact match already in the project first (e.g. the same icon from the icon package the
   app uses). Otherwise download each asset the way the `get_design_context` response describes, into the
   project's asset folder. No placeholders, no new icon package, no temporary Figma asset URLs left in code.
6. **Build** with approved tokens and primitives only — tokens, not magic numbers. A Figma value with no
   token → stop and ask (a missing-token task: Job 1 or the user). The reference code is a starting point
   to adapt, not to paste.
7. **Author the story with `sb-stories`' conventions** (materially different states only; a factory when 3+
   stories share a shape), stamp the node-id in a top-of-file comment and `parameters.design` (Shared
   plumbing), then validate light / dark / mobile against the captured screenshot.
8. **Record the delivery in the Figma Inventory** — so the stories this feature created don't scatter
   across the taxonomy. Idempotent; re-run per delivery, stories union by id:
   ```bash
   python3 scripts/record-figma-delivery.py . \
     --figma-url "<board url>" [--feature "<Name>"] --spec-url "<spec node url>" --node-ids 101-9717 \
     --description "<one line>" \
     --story "Hunts/Hunt Packs:hunts-hunt-packs--default:component"   # repeat per story you created
   ```
   Then ensure the root surface exists: scaffold once with `scaffold-wrapper.sh --figma`, drop the
   `figma-inventory.stories.tsx` template (title `Figma Inventory`), add **one export per feature**
   (`export const Hunts = { args: { feature: 'Hunts' } }`), and pin it to the top in `.storybook/preview`
   via `options.storySort.order: ['Figma Inventory', '*']`. The `FigmaInventory` wrapper reads
   `figma-inventory.json` and renders the index + each feature's board link + the stories it brought in.

### Job 3 — Connect: Storybook → Figma (code → design)

Push the code's truth back so Figma Dev Mode shows your real components. Code Connect needs an
**Organization or Enterprise** plan and **published library** components (see Preconditions). Follow
Figma's order; every call goes through the Job-0 store:

1. **Check access** — `whoami`. No Org/Enterprise plan → stop and say so; Dev Mode won't show mappings.
2. **Find the components** — `get_code_connect_suggestions` on the page or frame (`excludeMappingPrompt: true`
   for a lean list of unmapped components).
3. **Read each one** — `get_context_for_code_connect` per component node: its properties, variant options and
   descendants. Match it to the code component (a story with `parameters.design` for that node is the
   strongest signal; else the component name).
4. **Build the payload** — write `comps.json` (`component`, `codeFile`, `figmaNode`, optional `componentName`,
   `label`, `tokens`, `variantProperties`, `modes`), then:
   ```bash
   node scripts/build-code-connect.mjs --components comps.json --file <FILE_KEY> \
        --parity .storybook/figma-token-parity.json --usage .storybook/component-usage.json \
        --out .storybook/code-connect.json
   ```
   `send` is the exact `send_code_connect_mappings` input (`fileKey`, `nodeId`,
   `mappings[{nodeId, componentName, source, label}]`); `context` holds tokens, real props and variant →
   prop mappings for review; `reverseParity` lists components with no Figma node or no source, unsupported
   labels, drifted tokens.
5. **Confirm, then send** — show the user the mappings; on approval call `send_code_connect_mappings` with
   `send` as-is. One mapping only: `add_code_connect_map` works too. Both write to Figma.
6. **Prop-level snippets (optional)** — a plain mapping shows the import and component. To map props (the
   variant picker → code props), write a Code Connect **template**: `Component.figma.ts` for the
   `@figma/code-connect` CLI 2.x (template files are the only maintained format since 2.0; `.figma.tsx` +
   `figma.connect()` are migrate-only), or pass `template` in the mapping. Figma's `/figma-code-connect`
   skill covers templates; `context[].propMappings` is the input.

**Components with no Figma node** (`componentsWithoutNode`) have nothing to connect to; creating them is a
write (below).

The loop closes: the `parameters.design` node-id added when delivering (Jobs 1–2) is what Job 3 reads to
connect back.

## Writing to Figma (opt-in, off by default)

Only on the user's explicit request, and confirm before each write — it changes their file. Needs a Full
seat for an existing file (see Preconditions).

- **Default: `use_figma`** (load `figma-use` first, `skillNames: "figma-use"`). Build from the file's own
  components and variables (`search_design_system`), return every created node id, and record them.
- **Tokens → Figma variables** (create a collection or update one, Light + Dark):
  ```bash
  node scripts/build-figma-variables.mjs --css "src/**/*.css" --prefix "--color-bg,--color-text-" \
       --collection "Code tokens" --out .storybook/figma-variables-push.json
  ```
  Paste one entry as `PAYLOAD` into `references/write-figma-variables.js` and run it with `use_figma` —
  **`DRY = true` first**: it returns what it would create and update. Show that to the user, then run with
  `DRY = false`. It reuses a collection or variable with the same name and overwrites values; it deletes
  nothing. Check with Job 1: reading the collection back and running parity should show zero drift.
- **`generate_figma_design` only to capture a rendered page the first time** — e.g. a story rendered in
  isolation: `http://localhost:6006/iframe.html?id=<story-id>&viewMode=story`. It makes a pixel copy; refine
  it with `use_figma` against the library components. Updating a page already captured → `use_figma`.
- After a write, the new node id goes back into the story's `parameters.design`, so Jobs 2–3 see it.

## Shared plumbing — the Figma design embed (Docs)

Every story sb-figma touches gets the design source preserved on its catalog page:

```ts
parameters: { design: { type: 'figma', url: 'https://figma.com/file/<FILE_ID>?node-id=<NODE_ID>' } }
```

This is `@storybook/addon-designs` (the "Design" tab — the same mechanism `sb-explore` uses for frames). Plus
a node-id stamp + link in `parameters.docs.description`. `sb-explore` and `sb-stories` can reuse this snippet
whenever the node-id is known.

## Boundaries — never duplicate a sibling's verb

- **Exploration / "try a v2" / undecided** → `sb-explore` (Lab). sb-figma is for *approved* designs only.
- **Graduating a Lab experiment** → `sb-ship` (preserve `cp`, rewrite callsites). sb-figma delivers from
  *Figma*, not from a `/explore/` experiment.
- **Documenting an existing code component (no Figma)** → `sb-stories`. sb-figma *calls* sb-stories' rules to
  author; it never reimplements them.
- **Code-internal token health / orphans** → `sb-health` / `sb-inventory` (Figma-free). sb-figma adds the
  *design↔code* parity those can't see.

## Inputs the agent must resolve first

- **Figma file URL** → the `fileKey` every tool needs; a **node URL** for Jobs 2–3 (and for the
  `get_variable_defs` fallback: a frame that uses the tokens). Ask if not pasted.
- **Token CSS path(s)** — where `--token: <value>` declarations live (e.g. `src/styles/**/*.css`).
- **Is the design approved?** If the user is still iterating → stop, route to `sb-explore`.
- **Plan and seat** for Job 3 and writes — see Preconditions (`whoami`).

## Next

Run Job 1 (tokens) first so components built in Job 2 consume real, parity-checked tokens. Append progress to
`.storybook/audit/status.md` for clean resume. Full call sequence + the OKLCH→hex notes:
`references/figma-token-sync.md`.

Files in this skill

  • CONTEXT.md15.6 KB
  • SKILL.md21.1 KB
  • agents/openai.yaml2.1 KB
  • decorators/withLayoutFrame.tsx2.6 KB
  • references/figma-token-sync.md5.5 KB
  • references/read-figma-variables.js2.8 KB
  • references/workbench.schema.json1.9 KB
  • references/write-figma-variables.js4.5 KB
  • scripts/build-code-connect.mjs8.6 KB
  • scripts/build-figma-variables.mjs3.3 KB
  • scripts/build-token-parity.mjs10.3 KB
  • scripts/capture-figma.mjs5.3 KB
  • scripts/css-tokens.mjs9.7 KB
  • scripts/pull-figma-variables.mjs8.7 KB
  • scripts/record-figma-delivery.py5.5 KB
  • scripts/workbench-settings.mjs7.4 KB
  • templates/figma-inventory.stories.tsx1.4 KB
  • wrappers/FigmaInventory.tsx8.6 KB
  • wrappers/ReportIntro.tsx13.2 KB
  • wrappers/TokenMatrix.tsx27.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.