name: figma-design-systems description: >- Apply a named visual system to FigJam boards through the official Figma MCP: palette, typography, panels, implementation-code panels, ERD tables, diagram nodes, and connector notation. Use when styling a FigJam board, generating Pufferlib diagram furniture, or matching the tldraw design-system workflow in Figma.
figma-design-systems
Make FigJam diagrams obey a named visual system while remaining native,
editable board content. Read assets/systems.js; ACTIVE selects the system.
This is the MCP counterpart to tldraw-design-systems. The durable unit is a
skill plus deterministic native node groups, not a board-local script. Probe the
target editor before assuming components or embedded scripting; when that
surface is unavailable, regenerate named groups through use_figma.
Required skills and MCP
Use the OAuth figma MCP server, not a similarly named app connector. Load
figma-use and figma-use-figjam before any use_figma call, and pass:
skillNames: figma-use,figma-use-figjam
For a /board/ URL:
- Extract the file key and convert
node-id=1-2to1:2. - Inspect with
get_figjam; use0:1for the root. - Preserve every existing node and choose clear space from the returned bounds.
- Generate incrementally with
use_figma, returning every created or mutated ID. - Verify the wrapper section with
get_screenshot.
Use whoami when access fails so the user gets the exact OAuth identity to
share with. get_metadata is for Design files; inspect FigJam with get_figjam.
System contract
Read the active record from assets/systems.js before drawing. Local files are
not importable inside the remote Plugin API runtime, so inline the selected
record and helpers into each focused use_figma call.
Create one named wrapper section when the board needs a reusable specimen:
<System name> FigJam design system
Use deterministic child names:
[<System name> Kit] Panel / Default
[<System name> Kit] Panel / <Tone>
[<System name> Kit] Panel / Code
[<System name> Kit] ERD Table
[<System name> Kit] Node / <Role>
[<System name> Kit] Connector / <Meaning>
Names are for discovery and idempotent updates. Do not store workflow state in plugin data and never use a fuzzy name match to authorize deletion.
Pufferlib furniture
Panels
A panel is one transparent frame containing, back to front:
- An SVG dot-crosshatch shadow offset by the system shadow value.
- A filled rectangle with the system stroke weight and tone-colored border.
- A canvas-colored legend plate crossing the top border.
- A JetBrains Mono legend and body.
Keep the shadow inside the owner frame so moving, resizing, cloning, and duplicating remain one operation. Do not pair independent sibling shapes.
Implementation-code panels
Use the label code that does this · <area>. The visible content order is:
- Real repo-relative file path.
- Owning symbol or scope.
- Pseudocode.
Inspect the source tree before naming the path. A native FigJam code block is
preferred for the pseudocode body. Load Source Code Pro Medium before setting
its code; its internal font is independent of the surrounding JetBrains Mono.
ERD tables
Match the Pufferlib fieldset anatomy: a square outer border, the entity name on a background plate crossing the left edge of the top border, and the engine or status on a matching plate at the right. Those legends are separate nodes, not table rows.
Build compact ERDs as a transparent frame containing a square SVG grid and
editable text nodes. Do not use a native FigJam table: its 64px minimum rows and
rounded corners cannot match the Pufferlib specimen. Use 34px body rows with
KEY, FIELD, and TYPE columns, then overlay compact outlined PK, SK,
and FK badges. Keep explanations out of TYPE; put constraints and lifecycle
notes in the footer band. Keep the 8px crosshatch offset in the SVG and add the
system's subtle drop-shadow effect to the grid frame.
Manual connector drops resolve to the outermost layer under the cursor.
Frames, groups, and component internals block descent — a nested node binds
fine through the API but a human's drag stops at the container border. The
only container FigJam descends into is a section. So a hand-connectable
ERD table is a nested SECTION (named the entity, transparent fill), holding:
- The whole visual stack (grid, badges, texts) in one frame at (0,0), locked — so dragging the table face moves the section, not the frame.
- Per row and per side, a visible 16×16
SHAPE_WITH_TEXTellipse port (canvas fill, ink 1.5 stroke) centered on the face border, a direct child of the section:
[<System name> Kit] ERD Anchor / <entity>.<field> / L
[<System name> Kit] ERD Anchor / <entity>.<field> / R
The ellipse IS the port — visual affordance, manual drag target, and
connector endpoint in one node. Do not pair an invisible hit area with a
decorative dot: AUTO attaches at the bounding-box edge midpoints, so on an
invisible square the line lands up to half its size off the visible dot,
while on an ellipse every midpoint lies on the circle and connections read
centered from any approach. Table-level relationships bind the section
itself.
Connect relationships between anchors with bound, elbowed connectors, and
pin each magnet to the port's outward side — RIGHT on / R anchors,
LEFT on / L anchors:
connector.connectorStart = { endpointNodeId: sourceAnchorR.id, magnet: 'RIGHT' }
connector.connectorEnd = { endpointNodeId: targetAnchorL.id, magnet: 'LEFT' }
Never leave scripted endpoints on AUTO: it re-picks the attachment side on
every move, and on a column of collinear ports it will attach TOP/BOTTOM and
route the line vertically through every port on the edge.
Point FK/SK toward the referenced entity, label the field and cardinality, and use dashed lines only for legacy or non-authoritative relationships. Row-bound connectors follow the whole ERD frame when it moves.
assets/erd-table.js is the deterministic builder for one table — edit SPEC
and inline the whole script into a single use_figma call per table. It
carries the exact geometry (34px rows, column rules at x95/x265, footer band,
title and engine plates, port placement) plus the connector-wiring recipe, so
regeneration and new boards stay pixel-identical to the specimens.
Diagram nodes
Use createShapeWithText for connectable flow nodes. Set fill, stroke, and text
colors together. Measure text before resizing; non-rectangular shapes need a
larger text-safe area. Default connectors to 2px, elbowed, with an open arrow at
the end.
Expensive lessons
- Every text mutation is
loadFontAsyncfirst, then mutation, then returned ID. JetBrains Monois the Pufferlib face. QuerylistAvailableFontsAsync; use the exact installed style names.- A new connector label has no usable font until one is explicitly assigned.
clone()parents the copy undercurrentPage; append it to the intended section before setting section-local coordinates.- Append nodes to a section first, then position them. Coordinates change to the section's local space on reparent.
use_figmafailures are atomic. Read the error, correct the script, then retry.- Keep each
use_figmacall under ten logical node operations and validate each major specimen before building on it. - Create no FigJam pages. Organize the single board with sections.
- Crosshatch shadows created from SVG must be regenerated at the final bounds; resizing the SVG scales the pattern instead of preserving dot density.
- The crosshatch is one generated
<path>of dots — 7×13 staggered tile, r=1,#0f172aat 0.32 — not per-dot nodes and not an empty rect. Build the path string in a loop inside the script (seeassets/erd-table.js). - After creating or rebinding a connector, read back
connectorStart/connectorEnd. A binding that degraded shows the section id or a barepositioninstead of the row-shape id; fix it by reassigningendpointNodeId+magnet. Bindings can degrade later from unrelated edits — re-audit before trusting them. - Manual connector attach resolves the outermost layer: frames, groups, and component internals block descent to children; only sections descend. Anything a human should connect by hand must be a native object sitting as a direct child of a section (or the canvas). API bindings ignore this rule, which is how agent-drawn connectors mask the problem.
- Sections nest inside sections in FigJam, and
SHAPE_WITH_TEXTclamps to a 16×16 minimum. Elbowed connectors rejectmagnet: 'CENTER'; pinLEFT/RIGHTto the port's outward side, and make small endpoints ellipses so every edge-midpoint attachment lies on the visible circle. - Lock the visuals frame inside a table section: drags on the locked face fall through to the section, so the table still moves as one unit.
Verification
Read IDs and hierarchy with get_figjam. Then screenshot the wrapper section
and check:
- all named groups are children of the wrapper;
- no specimen overlaps another;
- table footers start below the measured table bounds;
- legends cross the border cleanly;
- crosshatch shadows stay attached to their owner groups;
- connectors remain bound after reflow;
- existing board content is unchanged.
