design-flow
Skill0 starsChanged 30 days agoMITView on GitHub
Design screens in Figma with their flowchart drawn right beside them, so each page states what it shows and how it behaves, and a developer can build it from one look. Uses the Figma MCP. Use when the user shares a figma.com/design URL and wants to analyze how the file is organized, add a flow to a screen or menu, design a new page with its flow, check which screens still lack a flow, or turn screen + flow into an implementation checklist. Trigger on "flow on the design page", "flowchart next to the screen", "design with flow", "user flow in Figma", "screen flow handoff", "ออกแบบหน้าพร้อม flow", "ใส่ flowchart ข้างหน้าจอ", "flow ของหน้านี้". Not for standalone FigJam boards with no screens (use figma-generate-diagram) or for plain design-to-code with no flow (use figma-design-to-code).
9 other projects use a version of this.
Adopted by
Most recently taken up by
- drinkspiller/skillstheir own version, first seen Nov 6, 2026
- taro1037-design/claude-skillstheir own version, first seen Oct 20, 2026
- hakushiidev/claude-skillstheir own version, first seen Oct 15, 2026
- rynbol/claude-skillstheir own version, first seen Oct 13, 2026
- DanielNoamTuby/claude-skillstheir own version, first seen Sep 20, 2026
- chipweaver/veripowertheir own version, first seen Sep 20, 2026
Other versions9 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 9
- julianoczkowski/designer-skills★ 536same name
- chipweaver/veripower★ 10same name
- viknesh20-20/claude-code-tool-kit★ 7same name
- Nihalgraphics/AI-Product-OS-for-UI-UX-Brand-Prototype★ 2same name
- drinkspiller/skills★ 1same name
- DanielNoamTuby/claude-skills★ 0same name
- hakushiidev/claude-skills★ 0same name
- rynbol/claude-skills★ 0same name
- taro1037-design/claude-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository8 other files it gives its agents.
- dependency-vuln-explainerSkill
- design-coreSkill
- doc-style-builderSkill
- pdf-operationsSkill
- poc-spec-driven-builderSkill
- podman-disk-reclaimSkill
- prompt-cache-kitSkill
- thai-mock-namingSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
20 rules. 0 are common, 9 are distinctive, and 11 are very specific to this project.
0
Common
Most of the 9 edited versions we read keep these.
9
Distinctive
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 39
Every layer this skill creates or imports uses the file's own components, variables and text/effect styles.
- Very specific: line 42
Before building, take an inventory: local variable collections, text, paint and effect styles, and components (search_design_system covers …
- Very specific: line 44
If something exists, use it: instances of components, setBoundVariableForPaint for colors, setTextStyleIdAsync for text, and bound …
- Very specific: line 46
If something is missing (no components, or a color with no variable), stop and tell the user exactly what is missing and ask whether they …
- Very specific: line 49
After any import, run python3 scripts/figma_js.py tokenize --page <id> --root <id> and pass its output as code to use_figma.
- Very specific: line 92
Write --to layers output inside the --work folder and give import_html_layers the path relative to /work.
- Distinctive: line 116
Before placing anything, find the section labels (wide instances of the title component near the page top) and their x-ranges.
- Distinctive: line 125
When a screen that already has a design changes:
- Very specific: line 217
Run design_graph.py extract --page <id> … --out DIR and then build DIR before larger work.
- Distinctive: line 247
Work in stages, and check each one before starting the next: Don't run everything in one go and look only at the end.
- Distinctive: line 257
When a check fails, fix that stage.
- Distinctive: line 263
Near targets get a line, far targets get a connector marker: When another screen sits between a flow and its target state, a long line …
- Very specific: line 268
A flow stays beside its own screen: When the next band of steps would run below the screen, beside the next screen down, it starts a new …
- Very specific: line 275
Graph: every flow render returns graph: what was actually drawn (nodes, edges, drawn/forced, orphans, missing, backward).
- Very specific: line 327
If the result is too large (big UI pages run to megabytes), it gets saved to a file.
- Very specific: line 340
Read the flow edges with one read-only use_figma call per page.
- Distinctive: line 359
Every decision has exactly two labeled outgoing edges.
- Very specific: line 366
Placement: Put the flow lane to the right of the screen frame, on the same top edge, with a 200 px gap.
- Distinctive: line 374
Write only where the user pointed you.
- Distinctive: line 382
States: default, empty, loading, and error, taken from the screen frame and its placeholder variants.
“Common” is measured: kept by at least 6 in 10 of the 9 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Design Flow
A screen in Figma shows what the page looks like. A flowchart placed beside it shows what the page does: which decisions it makes, where it goes on success, which alert it shows on failure, and which role can see it. This skill keeps the two together on the same canvas and in the same naming scheme, so the handoff becomes one checklist per screen.
It works on the file's existing conventions. It reads them before it draws anything and does not bring in a style of its own.
Setup, everyday use and the step-by-step agent runbook are in README.md. Read its Part 3 before the first Figma call in a session.
Before any call
- Parse the URL:
figma.com/design/<fileKey>/<name>?node-id=<a>-<b>gives youfileKeyandnodeId = "<a>:<b>". create_sessionwith the fileKey and a one-line task description. Pass the returnedsessionIdon every later file-tool call.- Load the official Figma skills when they're needed. Don't copy their content here:
figma-usebefore anyuse_figmacall. Addfigma-generate-designbefore a call that makes visible changes.figma-design-to-codebeforeget_design_context.- Tool order and limits come from https://github.com/figma/mcp-server-guide.
- Principles. Load the
design-coreskill. Its composition, writing and interface-quality rules apply to every screen, state screen and flow step this skill makes, and its review table is the design half of every check below. The file's own system still comes first. - Privacy. File contents are the user's. Don't copy real labels, node IDs, or screenshots into this skill, into commits, or into any public artifact. Examples use mock labels; see references/flow-conventions.md.
Rule: the file's design system first, never raw values
Every layer this skill creates or imports uses the file's own components, variables and text/effect styles. A raw hex, px size, or font is a gap to report, not a value to invent.
- Before building, take an inventory: local variable collections, text, paint and effect
styles, and components (
search_design_systemcovers published libraries too). - If something exists, use it: instances of components,
setBoundVariableForPaintfor colors,setTextStyleIdAsyncfor text, and bound variables for spacing and radius. - If something is missing (no components, or a color with no variable), stop and tell
the user exactly what is missing and ask whether they want it added
(
figma-generate-library). Don't create tokens or components without that yes. - After any import, run
python3 scripts/figma_js.py tokenize --page <id> --root <id>and pass its output ascodetouse_figma. It binds exact-match colors to variables, maps captured fallback fonts back to the real family (--font-map "Fallback=Real"), applies the text style of the same size, and returns what stayed unmatched.
Tools and backends
Everything is Python. Figma edits run as Plugin API JavaScript inside Figma, through one of two backends:
| Backend | How | Limits |
| figma-bridge (default) | gethopp/figma-mcp-bridge server in a container + its Figma desktop plugin. Tools: run_script, import_html_layers, get_document, create_*, set_*… | No Figma API/MCP quota. Needs Figma desktop with the plugin running in the file |
| Official Figma MCP | use_figma, generate_figma_design, get_screenshot | Counts against the plan's MCP tool-call limit (Starter runs out fast) |
Python can't edit layers directly. The Plugin API only exists inside Figma, and the REST API
can't write nodes. So scripts/figma_js.py renders the JS templates in
scripts/js/, and the agent passes that output to run_script (or use_figma).
| Command | Does |
bridge.py up --work DIR --run-script | builds and starts the bridge container (podman or docker): 127.0.0.1:1994 for the plugin, DIR mounted at /work |
bridge.py mcp | the MCP command; each session exec -is into the container (first = leader, others follow) |
bridge.py status / logs / down | container lifecycle |
capture.py ... --to layers --out DIR | page → html-figma JSON for import_html_layers (no quota; absolute layout) |
capture.py ... --to figma --capture ID | page → Figma via generate_figma_design capture ids (auto layout, binds variables; uses quota) |
figma_js.py swap | layers that match a component variant (structure, size, radius, fill token, vector geometry) → instances, texts carried over |
figma_js.py flow-kit --tokens t.json | builds the design-mode flow kit (Flow/Node, Flow/Decision, Flow/Marker, Flow/Element Row) from your tokens |
figma_js.py flow --spec flow.json | UI → flow → UI: element on screen → kit steps → next screen, lines routed around everything (A*) |
figma_js.py state --spec state.json | target state screen: copy + dialog overlay, or captured page + accurate sidebar |
figma_js.py make-room | shift a section right and widen its label so a flow fits (ask first) |
figma_js.py extract + design_graph.py | design knowledge graph: build, dupes, impact, tour, gaps |
figma_js.py autolayout | absolute frames → auto layout (rows and columns inferred, deepest first) |
figma_js.py tokenize | raw values → the file's variables and text styles |
figma_js.py place | new screen → placeholder slot (replace) or old screen slot (version card + old at 0.5) |
ui_scan.py + ui_components.py | code scan → component plan → build/extend components with simple State/Label/Icon controls → swap copies for instances |
flow_review.py DIR | strict 1–10 review of rendered flows from their graph JSON (hard gates: orphans/missing ≤6; forced, backward, ANY line crossing a line, nested anchors ≤8; pass ≥9) |
*.py --self-test | built-in checks |
One-time setup: claude mcp add figma-bridge -s user -- python3 <skill>/scripts/bridge.py mcp.
Then in Figma desktop: Plugins › Development › Import plugin from manifest (the plugin/manifest.json
from the bridge release), and run it in each file. run_script executes arbitrary code in the open
file, so it stays off unless --run-script is passed. Pass allowRemove: true for place.
Write --to layers output inside the --work folder and give import_html_layers the path relative to /work.
Converting things into Figma (all with auto layout)
| Source | Tool | Then |
| Live web page (public or signed-in) | capture.py url --to layers → import_html_layers (or --to figma with capture ids) | autolayout (layers only), tokenize, swap repeated blocks for components |
| Local HTML file or folder | capture.py html (serves on 127.0.0.1) | same |
| Many pages | capture.py jobs jobs.json | same |
| Image or screenshot | upload_assets puts the image on the canvas as a locked reference; rebuild over it with use_figma from components and figma.createAutoLayout(); compare with get_screenshot; delete the reference | — |
capture.py runs headless Chrome via uv run. It blocks every write to the target host
unless you pass --allow-writes, so a signed-in capture can't change data. login saves a
Playwright session file and never prints credentials. --to figma captures come back mostly as
auto layout with variables bound (poll generate_figma_design until completed). --to layers
captures come back absolutely positioned, so always follow them with autolayout, then tokenize.
Canvas layout: read top, work down
Many files mark each product area with a very large section label: an instance of a title
component stretched across the area's full width at the top of the page (for example
App A, App B, Mobile). Smaller labels for role, then menu, sit below it, and the screens
go below those. Read the page from the top down.
- Before placing anything, find the section labels (wide instances of the title component near the page top) and their x-ranges. A new screen goes inside the x-range of its section, under its role and menu label. It never goes at the page edge or (0,0).
- A new section gets its own label instance of the same component, to the right of the last section, at the same y.
- Replacing a placeholder keeps its slot, so the order is already right.
Versioning on the canvas
When a screen that already has a design changes:
- The new version takes the screen's place and keeps the breadcrumb name.
- A version card sits above it: a large frame built from the file's tokens (largest
text style, theme color variables) that says
New version, the app version or date, and what changed in one line. - The old version moves directly to the right of the new one (200 px gap), gets
opacity = 0.5, and has its version added to its name:Role ▸ Menu ▸ Screen (v1.2.20). Never delete it. - A screen that only held a "not started" placeholder has no old version. Replace the placeholder.
figma_js.py placedecides which case applies. When a card token or text style is missing it returnsblockedwith the list, and the agent asks the user instead of inventing one.
Rule: every component is used in the design
A component that sits only in the Components band is unfinished work. Creating one is one step; the work is done only when the design uses it:
- Create → swap in the same run.
ui_components.py applybuilds a component and replaces its copies on every screen, then reports anything still unused. - After building anything new (capture, state screens, new pages), run
ui_components.py sync --page <ui page> --ignore '^Flow/'. It swaps every component into every screen, including screens built after the component existed. - Check:
figma_js.py usage --page <ui page>(read-only) lists instance counts per component and variant.unusedandunusedVariantsmust be empty. If a component or variant still has 0 instances, either make it match its copies (fix the variant) or remove it. A duplicate of another component gets merged into it. Never leave a component idle. - New screens are built from instances. A plain frame that matches a component is a bug, not a style choice.
Components from the codebase (fastest path)
The app's code already says what each component is and which states it has, so build Figma components from it instead of guessing from repeated pixels:
python3 scripts/ui_scan.py scan <src dirs> --out inventory.json # every JSX component used, with props
python3 scripts/ui_components.py names --map map.json --out names.json # code component → layer-name regex
python3 scripts/figma_js.py ui-samples --page <ui page> --spec names.json -o samples.js # run: read-only
python3 scripts/ui_components.py plan --inventory inventory.json --samples samples.json --map map.json --out plan.json
python3 scripts/ui_components.py apply plan.json --page <ui page> --components-page <page>ui_scan.py(stdlib, under a second for about 800 files) counts uses per component and import source, and records literal prop values (variant,size,bg,theme…) and state flags (disabled,loading,checked,isInvalid,readOnly). It also records label texts and icon use. It ignores TS generics, comments, spreads and layout plumbing (Col,Row,Controller…).map.jsongives each code component a simple Figma name and the layer-name regex captures use (e.g.{"Form.Control": {"figma": "Input", "layers": "^(Input|FormControl)$", "text": ["Value"]}}).ui-samplesgroups matching layers by structure and splits each group by fill token: each group is one component, and each fill in it is one variant.Simple controls:
Statevariants are named after the code's own values when the fill token matches (green →success, primary/blue →primary, outline →outline-primary).- Text properties
Label/Value/Hint. - An
Iconboolean.
One switch changes a state. Colors stay bound to variables.
- Review the plan before
apply. Make a contact sheet of every planned component's samples (get_screenshoton the sample ids) and rename inplan.jsonwhere the code name misleads. A layer namedButtonorList Itemcan be a sidebar header, and two near-identical groups can be different fields (lookup vs date). Keep names short and honest, e.g.Icon Buttonwithedit/view, andSidebar Itemwithdefault/active. When an existing set already uses a state name for a different look (another app's theme), give the new look its own value instead of merging into the wrong one. - Icon buttons get one state per icon, named by the action (
edit,delete,print,refresh…).swapmatches on vector geometry, so a copy whose icon has no variant stays a plain frame.ui-samplessplits by geometry for exactly this reason. Re-runui-samplesafterapply: whatever is still listed did not match. - Components live in the file's own Components band.
componentizeplaces its section under the title band whose label equals the section name (e.g. a titleInfo "Components"), right of what's already there. When no such band exists the result says so: add the band and move the section instead of leaving it at the page's right edge. - Existing components are extended, not duplicated: missing variants are added to the existing set on its own
axis. Then
swapreplaces every matching copy on every screen with an instance, carrying the texts over. Re-run the flow review afterwards; anchors that matched by text still resolve on instances.
Lines never cross lines
- No crossing is acceptable. Your own flow's lines and other flows' lines are both a review fail (capped at 8), not a small deduction.
- Steps sit next to what they come from. Bands (one per anchor) are stacked in the order their lines leave the
screen. When the result still crosses, the renderer stacks them again by where each line actually crosses the
screen's right edge, routes again, and keeps the version with fewer crossings (
reorder: falseturns this off). - Titles stay clear. The strip above a screen, where Figma draws its name, is an obstacle (
title_strip, default 120 px). - Anchor by component, not by node id.
{component: "^Pager Arrow$", state: "^next"}finds an instance by its component and variant, so it survives swaps and rebuilds. Anodeid breaks as soon as the layer is swapped for an instance. Usefind(visible text) orcomponent/state, andnodeonly as a last resort. - Anchors must pick the element, not its container. Two anchors resolving to boxes inside one another (e.g.
find: "Search"hitting the field label's group instead of the button) makes lines that start inside each other and cross. The graph lists them asnested; fix the spec withwithin: "^Button"orname.
Knowledge graph (adapted from Understand-Anything)
Run design_graph.py extract --page <id> … --out DIR and then build DIR before larger work.
The graph is deterministic and incremental (fingerprint per screen) and has typed nodes
(screen, component, variable, text_style, label, flow) and edges (uses_component, binds_variable,
uses_text_style, navigates_to, has_flow). Ask it questions instead of re-reading the file:
dupes: structures that repeat across screens but aren't components. These are the next components to make.impact "<token or component>": every screen that changes if it changes. Run it before editing a token.tour: screens top-down per section, with what each uses. Use it for onboarding or review order.gaps: placeholders, raw paints, unstyled text, screens with no flow.
Flows: UI → flow → UI
A flow joins real UI to real UI. An arrow leaves the actual element on the source screen (button, card, input, row action), runs through flow-kit steps, and lands on the screen that comes next: another page, or a copy of the screen in its resulting state (dialog open, alert shown). A flow is never a separate list of elements.
| Part | How |
| Steps | Flow Kit components (flow-kit): Process, Alert, Success, End, Decision. Design files can't create or clone FigJam shapes or connectors (Plugin API limit), so the kit is real components bound to tokens |
| Target states | capture.py jobs with steps (e.g. click "Import") and selector (e.g. the dialog). Then figma_js.py state: overlay = copy of the screen + scrim + the captured dialog; page = captured page + the accurate sidebar moved in from the source. A captured dialog is fitted to its content (captures keep page coordinates). Place with near: {screen, dx}: the first free slot right of the source screen, sliding down past anything already there, so state screens never overlap |
| Lines | figma_js.py flow routes every edge with A* on an orthogonal Hanan grid built from the edges of nearby objects (with a clearance margin), plus lanes one and two lane widths beside earlier lines. Cost = length + bend penalty + per-px penalty for running on or within a lane of an earlier line (other flows' lines included) + a penalty per crossing or touch. Short edges are routed first and long ones (to a remote screen) last. Two passes: the first picks each edge's sides, the second sorts the ends sharing one side of a box by where their lines head and gives them slots in that order, so lines don't swap at the box. An element's first run leaves straight, or jogs into a channel along a nearby element's edge (row divider, toolbar gap), whichever crosses fewer elements; another anchor's element counts triple. Lines never run over other screens or steps |
| Layout | Steps sit on a layered grid: column = steps from the element, rows aligned on their centers, 10px snap. The main path stays on one row; a decision's 2nd branch drops straight below its bottom vertex and its 3rd rises straight above its top vertex. Element bands stack in the order their lines leave the screen (clockwise: top side left→right, right side top→bottom, bottom side right→left), so neighbouring lines nest instead of crossing. A column gap widens when a label would not fit its run |
| Decisions | House convention: 1st outgoing edge from the right vertex (True), 2nd from the bottom (False), 3rd from the top. Labels sit just after the branch point: short ones on the line, longer ones beside it, never on another line or node |
| Room | Flows stay inside their section's x-range. When a section has no room, figma_js.py make-room --from-x X --dx D --label <section label> shifts everything right of X (connectors follow their endpoints) and widens the section label. Ask the user first. Moving glued layers this way leaves Figma connectors with their old stored route (the line stretches back to the old spot), and the API can't re-route or create connectors in design files. Run figma_js.py redraw-connectors right after: each stale connector is redrawn as a vector with the same ends, magnets, stroke (variables kept), caps and label, and the original is hidden, not deleted |
Pipeline: design → state → graph → strict review (loop)
Same discipline as a strict figure review: a wrong or missing arrow is never cosmetic.
Work in stages, and check each one before starting the next. Don't run everything in one go and look only at the end. When something is wrong in a single batch run, it's wrong everywhere and the lines hide the cause.
| Stage | Do | Check (show the user, wait for OK) |
| 1. Place | capture + build the state screens, make room | figma_js.py place-check: 0 screens overlapping (title strip included), none on a band title. Screenshot one row with screens only, no lines |
| 2. Lines, one row | render the flows of that one row | screenshot of the row with lines, plus flow_review.py: no crossings, no missing edges |
| 3. Lines, everywhere | render the rest | flow_review.py on everything, plus screenshots of the worst-scoring rows |
| 4. Components | ui_components.py sync | figma_js.py usage: nothing unused |
When a check fails, fix that stage. Don't carry the problem into the next one.
- Design (spec): one spec per screen, from the app source. Every step must be reachable from an element.
- State: capture the target states (
capture.py jobswithsteps/selector; reports that read by POST need--allow-post <read-only regex>). Build them withfigma_js.py statein one row per source: source | its steps | state 1 | its steps | state 2 … Sources never move. Crop page states to the source height (clip_to).- Near targets get a line, far targets get a connector marker. When another screen sits between a flow and its
target state, a long line would loop past those screens and cross everything on the way. Instead, the step
chain ends in a lettered
Flow/Marker(A, B…), and the same letter sits right before the target with a short arrow in (Marker · <screen id>). That marker is created once and shared by every flow that goes there. This is the standard flowchart on-page connector. Off withmarkers: false. - A flow stays beside its own screen. When the next band of steps would run below the screen, beside the next
screen down, it starts a new column of bands to the right, back at the top (
wrap: falseto turn off). Size the gap before the first state for it. - Lines leave an element on the side facing the flow. Leaving on the back side wraps around the screen and costs
back_side_cost(2500). - Packed pages rule out stacking states in a column: with screens about 1.2× their height apart, only one row fits.
- Near targets get a line, far targets get a connector marker. When another screen sits between a flow and its
target state, a long line would loop past those screens and cross everything on the way. Instead, the step
chain ends in a lettered
- Graph: every
flowrender returnsgraph: what was actually drawn (nodes, edges,drawn/forced,orphans,missing,backward). Save it as<name>.graph.jsonwith<name>.qa.json. - Strict review:
flow_review.py DIRscores every flow 1–10.- Hard gates: a missing edge or orphan step caps the score at 6; a
forcedline (the router fell back) or a line reading backward (right→left, bottom→top; returns to a screen are allowed) caps it at 8. - Deductions: lines over objects, label overlaps, crossings.
- Pass ≥ 9. Fix what the notes name (anchor text, states, room, spec fan-out), re-render, and review again, for a few rounds at most. Report the pass count and mean, never just "done".
- Hard gates: a missing edge or orphan step caps the score at 6; a
Reading order is left → right, top → down: bands in the order elements sit on the screen, columns by step
depth, a decision's True to the right and False below. Every node is connected: an edge the router can't place
is retried with a tighter clearance, then drawn as a plain elbow flagged forced. It's never dropped.
Spec per source screen (one more per state screen whose elements continue the flow):
{"screen": "<source screen id>",
"components": {"node": "<Flow/Node set>", "decision": "<Flow/Decision>"},
"tokens": {"edge": "<var>", "edge_true": "<var>", "edge_false": "<var>", "label_text": "<style>", "chip": "<var>"},
"anchors": [{"id": "a_import", "find": "Import"},
{"id": "a_view", "name": "^Button$", "within": "^Table Cell$", "nth": 0, "side": "right"}],
"screens": [{"id": "s_dialog", "node": "<state screen id>"}],
"nodes": [{"id": "n1", "type": "process|decision|alert|success|end", "text": "…"}],
"edges": [{"from": "a_import", "to": "s_dialog"}, {"from": "n1", "to": "n2", "label": "True", "tone": "true"}]}Anchor selectors (combine as needed): find = visible text (exact match first, then substring;
the text is lifted to its button or card), name = layer-name regex, node = exact node id,
within = regex on an ancestor's layer name (e.g. a table row), nth = which match (0-based,
document order), side = right|left|top|bottom to pin the exit (default auto). A row action
with the same icon in every row needs name + within + nth; find alone lands on the header.
An element clipped out by a parent frame is pulled to the frame's visible edge and listed in
clipped: fix the state screen when that happens.
The result reports anchors (resolved node ids), exits, failed, ms, and qa, a measured
quality check: crossings (between this flow's lines), crossOther (with other flows' lines),
lineOverObjects (runs through any box but the edge's own ends), minParallelGap (closest
overlapping parallel runs of different lines), labelOverlaps, bends, length, headInner
(elements crossed by first runs inside the source screen). Aim for zero lineOverObjects and
labelOverlaps; compare the rest before and after a change instead of judging by eye.
Take behavior from the app's source (click handlers, dialogs, validation, confirm and success
messages), never from guesses. Re-running replaces that screen's previous flow. Flows that point
at each other's screens see each other's lines, so render them in a fixed order.
bridge.py ensure relaunches the plugin when Figma has closed it.
Workflow 1: Analyze the file
Goal: a map of role → menu → screen and a list of which screens already have a flow.
get_metadatawithoutnodeIdto list the pages. Then run it on the page or frame the user named.- If the result is too large (big UI pages run to megabytes), it gets saved to a file.
Summarize that file with
python3 -I: walk the XML, print the top 2–3 levels, and count node types. Never read it raw. Classify the page's top-level children:
Node type Means framearound 1920 wide (desktop) or around 390 wide (mobile)a screen small frame/instancewith a title componenta section or menu header instanceof a "placeholder" or "working" componenta screen that isn't designed yet; read its variant for the status shape-with-texta flow step (SQUARE = process or alert, DIAMOND = decision) connectora flow edge - Read the flow edges with one read-only
use_figmacall per page. Use the script in references/flow-conventions.md. It returns each connector's label, its color, and the names of its endpoints. - Report: the screen tree, with screens marked has flow / no flow / placeholder, the naming pattern in use, and the flow kit you found. Point out names that don't follow the pattern or contain typos, but don't rename anything unless asked.
Workflow 2: Design a page with its flow
- Find the flow kit. This is the page or frame that holds the template shapes and connectors. Ask the user which one it is if it isn't obvious. Read it, then copy its shape types, fills, connector colors, line type, caps, and label words. The defaults in flow-conventions.md apply only when the file has no kit.
- Write the flow as text first and show it to the user:
Every decision has exactly two labeled outgoing edges. Every alert has an exit.[Screen 1.1 loads] → <Has permission?> --True--> [Show list] --False--> [Alert: no access] → [Back to Menu 1] - Build. Design-mode files block
figma.createShapeWithText()andfigma.createConnector(), so clone the kit's template nodes (node.clone()), then settext.characters(load the font first), position the clones, and setconnectorStart/connectorEndwithendpointNodeIdandmagnet: 'AUTO'. If cloning is also refused, stop and tell the user. Don't fake the flow with plain rectangles and lines unless they ask for that. - Placement. Put the flow lane to the right of the screen frame, on the same top
edge, with a 200 px gap. Decisions go in one row and outcomes go below them. Wrap the
screen and its lane in a
sectionnamed exactly like the screen. - Naming. The screen frame is named with the breadcrumb
Role ▸ Menu ▸ Screen. Each step's text is the user-visible action or check, written in the file's language. - Take one screenshot after composition. Fix what's wrong, take one screenshot after the fix, and stop.
Write only where the user pointed you. If they say "try it" or "test", work on a duplicated page, never on the live screens.
Workflow 3: Handoff checklist
For each screen, run get_design_context on the screen and read its flow (Workflow 1,
step 4), then write:
- States: default, empty, loading, and error, taken from the screen frame and its placeholder variants.
- Branches: one line per decision:
<condition> → True: <step> / False: <step>. - Alerts: the text and exit of each alert step.
- Role gates: decisions that test a role.
- Navigation: where each Base edge leads (menu item → screen).
- Gaps: decisions with a missing branch, alerts with no exit, and screens that are still placeholders.
Return the checklist in chat or as a doc the user picks. Code comes from
figma-design-to-code. This skill only supplies the behavior side.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

