Skip to content
Back to skills

Figma Click Flow

ASecurity

Draws a click-flow overlay on a section of laid-out Figma screens for static handoff: a dot on each clickable element, an elbow line with rounded corners and an open arrow at the destination screen. figma.createConnector is not a function in Figma Design files, so each connector is emulated with one SVG import (hardened recipe in references/draw-overlay.js). Use when the user asks for a "click flow", "flow arrows", "wire screens", "turn this into a flow", or in Portuguese "vira em fluxo", "se...

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 2, 2026
ai-agentsrustgoreactnodeawsgitapidocumentation

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

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

Scanned October 6, 2026

npx -y skills add thiagoxikota/figma-maxxing --skill figma-click-flow --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Figma Click Flow?

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

Security grade badge for Figma Click Flow
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/thiagoxikota-figma-click-flow/badge)](https://www.skillsdirectory.com/skills/thiagoxikota-figma-click-flow)

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: figma-click-flow
description: >-
  Draws a click-flow overlay on a section of laid-out Figma screens for static handoff: a dot on
  each clickable element, an elbow line with rounded corners and an open arrow at the destination
  screen. figma.createConnector is not a function in Figma Design files, so each connector is
  emulated with one SVG import (hardened recipe in references/draw-overlay.js). Use when the
  user asks for a "click flow", "flow arrows", "wire screens", "turn this into a flow", or in
  Portuguese "vira em fluxo", "setas do fluxo".
license: MIT
compatibility: >-
  Writes to a Figma Design file through figma-console-mcp (figma_execute, Desktop Bridge plugin
  in Figma Desktop) or through the official Figma MCP server (use_figma, Full seat, not
  validated by this repo; the recipe guards figma.loadAllPagesAsync, which Figma lists as not
  implemented there). Requires the figma-canon and figma-preflight skills.
metadata:
  author: Thiago Xikota
  version: "1.2.0"
---

# figma-click-flow: Click-flow handoff overlay

Draws a printed user-flow overlay on a section of laid-out screens. Each connection: a red dot at the click target on screen A, an elbow line with rounded corners ending in an open arrow at screen B's nearest edge. Reference look: a flow section of one production file.

**REQUIRED PREREQUISITE:** load `figma-canon` + pass `figma-preflight` before any `figma_execute` write (the write path is the figma-console MCP server through its Desktop Bridge plugin). With only the official Figma MCP server connected, the write goes through `use_figma` on path B of `figma-preflight` Check 0 (not validated by this repo: keep writes small, read back every change), and Phase 4 validates with `get_screenshot` within the budget in [`figma-canon/references/rate-limit-recovery.md`](../figma-canon/references/rate-limit-recovery.md).

## Why a separate skill (not createConnector)

`figma.createConnector` throws `not a function` in Figma Design files (verified via `figma_execute`, field note 2026-05). `CONNECTOR` nodes that already exist in design files were created via the UI (Shift+L shortcut) and **cannot be cloned**: `node.clone()` errors with `Cloning CONNECTOR nodes is not supported in the current editor`. This skill therefore emulates the connector look by importing one SVG per connection through `figma.createNodeFromSvg`.

## When to use

- A section of screens is already laid out and the user wants the printed click-flow overlay on top.
- The designer wired the prototype (`reactions` on nodes) and wants the same connections visible in static handoff.
- The user provides explicit `(from-element-id -> to-screen-id)` pairs.

## When NOT to use

- File is FigJam: use `figma.createConnector` directly.
- User wants to design layout: use your project's own build skill or workflow, if any, or ad-hoc `figma_execute` after `figma-preflight`.
- User wants Dev Mode component documentation: out of scope for this skill. Use your project's own handoff documentation workflow, if any.
- Section has > 40 connections: output becomes spaghetti. Split into smaller sub-sections first.

## Visual canon

Verified against a reference node in one production file. `refConn` in the right column is the reference connector.

| Property | Value | Source of truth |
| --- | --- | --- |
| Stroke color | `#E5484D` | Neutral default, configurable. The reference used one dominant red in 20+ instances; that value was project-specific |
| Stroke weight | 4 px | `refConn.strokeWeight === 4` |
| Path | Elbow with **rounded corners**, R = 12 px | `connectorLineType: 'ELBOWED'` + visual radius pixel-picked |
| Origin marker | Filled circle, r = 5 px (10 px diameter) | `connectorStartStrokeCap: 'CIRCLE_FILLED'` |
| End cap | Open arrow, two strokes back from tip at ±28°, length 12 px | `connectorEndStrokeCap: 'ARROW_LINES'` |
| Layer name | `flow-overlay · click-connectors` (kebab-case with a middle-dot separator, from one project's own layer canon; keep this exact name, because the rerun and the cleanup snippet find the overlay by it) | [`figma-canon/references/naming-canon.md`](../figma-canon/references/naming-canon.md) |

The stroke color `#E5484D` is a neutral default and is configurable: override it through the `strokeColor` input (`STROKE_COLOR` in the recipe) when the file has a brand-specific accent.

## Inputs

```yaml
sectionId: "123:456"            # required
mode: from-prototype            # default; OR "explicit" OR "auto-layout"
pairs:                          # used when mode === "explicit"
  - from: "123:789"             # element that gets tapped
    to:   "123:790"             # destination screen
triggerFilter: ["ON_CLICK", "ON_PRESS"]   # default; ignores hover/drag
includeOverlayActions: true     # default; sheets count as navigation
strokeColor: "#E5484D"          # default; see Visual canon
cornerRadius: 12                # default
strokeWeight: 4                 # default
originAnchor: "auto"            # default; "center" | "edge-toward-dest" | "auto"
```

Each input is a constant in the CONFIG block of `references/draw-overlay.js`: `sectionId` is `SECTION_ID`, `mode` is `MODE`, `pairs` is `EXPLICIT_PAIRS`, `triggerFilter` is `TRIGGER_FILTER`, `includeOverlayActions` is `INCLUDE_OVERLAY_ACTIONS`, `strokeColor` is `STROKE_COLOR`, `cornerRadius` is `CORNER_RADIUS`, `strokeWeight` is `STROKE_WEIGHT`, `originAnchor` is `ORIGIN_ANCHOR`. The recipe also has `DOT_RADIUS`, `ARROW_LEN`, `AUTO_LAYOUT_MIN_AR` and `AUTO_LAYOUT_ROW_GAP`, which have no input above.

**`mode` reference:**

- `from-prototype` (default): walks `reactions[]` on every descendant. Best when designer wired the prototype.
- `explicit`: uses `pairs`. Best when the caller has a known list (from a PRD, a spec that lists screen transitions, or a hand-curated list).
- `auto-layout`: groups top-level FRAMEs in the section into rows by Y, sorts each row by X, builds intra-row left-to-right pairs. Best when prototype isn't wired and screens are sequentially laid out (like a numbered catalog). Heuristic: verify visually before trusting; rows mixing scenarios may produce wrong connectors.
  - The name means pairs inferred automatically from the layout. It has nothing to do with Figma's Auto Layout feature.
  - Only portrait children count as screens: FRAME, COMPONENT or INSTANCE with height/width >= 1.3 (`AUTO_LAYOUT_MIN_AR`). This filters out labels and dividers. For desktop or landscape screens, lower that constant, or the run returns `drawn: 0`.
  - A screen whose Y is more than 100 px (`AUTO_LAYOUT_ROW_GAP`) below the previous one starts a new row.

**`originAnchor` reference:**

- `auto` (default): bbox center for small nodes (<200×200 = buttons, list rows), edge-toward-dest for screen-sized nodes (any node at least 200 px in both dimensions). Right behavior for both prototype-driven mode and screen-level adjacency.
- `center`: always bbox center. Use when prototype reactions are wired on small interactive elements and you want the dot ON the button.
- `edge-toward-dest`: nearest-edge midpoint. Use for screen-to-screen adjacency where center would put the dot deep inside the screen body.

If user just says "wire it up" with no detail, default to `mode: from-prototype`.

## Workflow (4 phases + cleanup)

### Phase 0: Sanity

Walk up to the page ancestor (sections may be nested inside sections), call `figma.loadAllPagesAsync()`, then `await figma.setCurrentPageAsync(pageNode)`. Use `figma.getNodeByIdAsync` exclusively: sync `getNodeById` errors under `documentAccess: dynamic-page`.

Server note for `use_figma` (official Figma MCP server): Figma's figma-use skill lists `figma.loadAllPagesAsync()` as not implemented in that runtime, and says a read of a member the node type does not have throws `TypeError: node.X: no such property 'X' on Y node`, optional chaining included ([figma/mcp-server-guide](https://github.com/figma/mcp-server-guide), read 2026-10). So the recipe calls `figma.loadAllPagesAsync()` inside `try`/`catch` and carries on without it (`setCurrentPageAsync` loads the page anyway), and it checks `"reactions" in n` before reading `reactions`, because SECTION, SLICE and COMPONENT_SET nodes do not declare it. In `figma_execute` both guards change nothing. This repo has not run the recipe on `use_figma`.

### Phase 1: Discover pairs

Walk descendants of the section. For each node, inspect `reactions[]`, and in each reaction read `actions[]` (the recipe falls back to the singular `action` field when `actions` is absent). Collect a pair when:

- `action.type === 'NODE'` (navigate to frame), OR
- `action.type === 'OVERLAY'` (open sheet/modal, counts as flow), AND
- `reaction.trigger.type` is in the `triggerFilter` (default: `ON_CLICK`, `ON_PRESS`, `ON_TAP`).

The recipe keeps a reaction that has no trigger.

Drop pairs where `from` or `to` cannot be resolved or lies outside the section's bounding box (likely external/cross-page links). Dropped pairs come back in the `dropped` array of the return value with a reason (`node not found` or `outside section`): report them instead of losing them silently.

If 0 pairs found, **return early** with a warning so the caller can switch to `mode: explicit` and supply pairs manually.

### Phase 2: Plan elbow geometry

For each pair compute (all in absolute page space):

- `origin` = anchor on `from` node's `absoluteBoundingBox`, picked by `originAnchor`: bbox center for small interactive nodes, nearest-edge-toward-dest for screen-sized FRAMEs (`auto` default), or as overridden.
- `dest` = nearest-edge midpoint of `to` frame's `absoluteBoundingBox` (left / right / top / bottom).
- Routing: a single L-shape through `(midX, origin.y)` then `(midX, dest.y)` where `midX = (origin.x + dest.x) / 2`. Same right-angle topology as `connectorLineType: 'ELBOWED'`.
- Corner radius = `cornerRadius` (default 12), inserted as Q-curves.

If `|origin.y - dest.y| < 2 * cornerRadius`, drop the elbow and draw a straight line (avoids degenerate corners).

### Phase 3: Draw

Per pair, build an SVG string with:

- `<circle>` at origin (origin marker).
- `<path>` with the elbow + Q-corners (the trunk).
- `<polyline>` arrowhead at dest.

Import via `figma.createNodeFromSvg(svgString)`, which returns a FRAME with vector children. Position the imported frame at `(minX, minY)` (the SVG's top-left in page space).

Coord-space rule (verified): SECTION children use **section-local** coords. After `section.appendChild(overlay)`, set `overlay.x = 0; overlay.y = 0` so overlay aligns with section's top-left. Then position each imported SVG frame as a child of `overlay` using overlay-local coords (subtract section absolute origin).

`vectorPaths.windingRule: 'NONE'` is accepted by the API (verified). Auto-fit moves a vector to its path's leftmost coord after `vectorPaths` is set; trust this rather than fighting it.

See [`references/draw-overlay.js`](references/draw-overlay.js) for the complete hardened snippet. The same snippet is written to run in `use_figma` of the official Figma MCP server too, with the guards described in Phase 0 (not validated on that server by this repo); the timeout figures below are for `figma_execute` only.

`figma_execute` has a default timeout of 5000 ms and accepts up to 30000 ms. After a timeout or an error, do not rerun at once: this recipe removes the prior overlay and then draws a new one, so first read [`figma-canon/references/figma-execute-atomicity.md`](../figma-canon/references/figma-execute-atomicity.md) and the section "figma_execute timeout on bulk clone and font loads" in [`figma-canon/references/plugin-api-anomalies.md`](../figma-canon/references/plugin-api-anomalies.md).

### Phase 4: Validate

Use `figma_capture_screenshot` (plugin export). REST-backed screenshots return HTTP 429 (rate limited) after a handful of calls in a batch; see [`figma-canon/references/rate-limit-recovery.md`](../figma-canon/references/rate-limit-recovery.md). **Do not** use `figma_take_screenshot` for large sections.

As of figma-console-mcp v1.40.8, `figma_take_screenshot` uses the Desktop Bridge plugin when connected and falls back to the REST API, while `figma_capture_screenshot` always uses the plugin runtime and needs the bridge. The rule above still holds: `figma_capture_screenshot` is the call that always stays on the plugin runtime.

Check:
- Every line starts on a real interactive element, not whitespace.
- Lines do not cut through unrelated screens (if so, raise corner radius or split into sub-sections).
- Origin dots sit on the correct element.
- Color, stroke weight, dot size match the visual canon table.

### Cleanup / remove overlay

Idempotent rerun: skill auto-removes any prior `flow-overlay · click-connectors` frame inside the section before drawing. To remove without redrawing (here `section` is the section node, from `await figma.getNodeByIdAsync(sectionId)`):

```js
const overlay = section.findOne(n => n.name === 'flow-overlay · click-connectors');
if (overlay) overlay.remove();
```

## Common mistakes

| Mistake | Fix |
| --- | --- |
| `figma.createConnector(...)` | Not a function in design files; emulate via SVG import |
| `node.clone()` on existing CONNECTOR | Throws "Cloning CONNECTOR nodes is not supported" |
| `figma.getNodeById` | Use `getNodeByIdAsync`; sync version errors under dynamic-page |
| Set `overlay.x = section.x` before append | Section children use LOCAL coords, produces double offset; set `x=0; y=0` AFTER append |
| Validate with a REST-backed screenshot | `figma_take_screenshot` returned 429 on the large sections of one production file; use `figma_capture_screenshot` |
| Filter only `action.type === 'NODE'` | Misses `OVERLAY` (sheet open), common in mobile flows; include both |
| Ignore `reaction.trigger.type` | Hover/drag triggers pollute the overlay; filter to ON_CLICK/ON_PRESS/ON_TAP |
| Draw nothing when prototype unwired | Detect 0 pairs and return early with a warning, not a silent empty overlay |
| Sharp 90° corners | Use Q-curves with R=12 to match reference rounded look |
| Passing `section.parent` to `figma.setCurrentPageAsync` | Walk up `parent` chain until `type === 'PAGE'`; nested sections break naive `section.parent` |

## Quick reference

| Need | Call |
| --- | --- |
| Discover prototype reactions | `node.reactions[].action.{type:'NODE'\|'OVERLAY', destinationId}` (the recipe reads `actions[]` and falls back to `action`) |
| Filter by trigger | `['ON_CLICK','ON_PRESS','ON_TAP'].includes(reaction.trigger?.type)` |
| Build connector SVG | `<circle>` + `<path>` (elbow w/ Q-corners) + `<polyline>` arrow |
| Import SVG | `figma.createNodeFromSvg(svgString)` returns a FRAME with VECTOR children |
| Default red | `#E5484D` (rgb 0.898, 0.282, 0.302) |
| Cleanup | `section.findOne(n => n.name === 'flow-overlay · click-connectors')?.remove()` |

## Cross-references

- `figma-canon` + `figma-preflight`: required before any `figma_execute` write.
- `figma-orient`: only run if file is unfamiliar this session; skip otherwise.
- `references/draw-overlay.js`: full hardened snippet, copy + parameterize for use. It is a `figma_execute` body (top-level `await` and `return`), not a standalone Node script.
- [`figma-canon/references/figma-execute-atomicity.md`](../figma-canon/references/figma-execute-atomicity.md): what to do after a `figma_execute` timeout or error before retrying.

Files in this skill

  • SKILL.md13.5 KB
  • references/draw-overlay.js10.3 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.