Create design trees with nested JSX markup. One jsx call builds a complete subtree atomically — nesting is the hierarchy. Keep a single logical unit inside one call; the returned root's children are already built, not stubs to be filled in later.
Examples:
jsx({markup: "<frame name='Card' layout='column' padding={16} fill='#FFFFFF' w='fill' />"})
jsx({markup: "<frame name='Row' layout='row' gap={8} padding={12} w='fill'><icon name='lucide:settings' size={20} /><text name='Label' w='fill'>Account</text><icon name='lucide:chevron-right' size={16} /></frame>"})
Elements: frame, text, rect, ellipse, line, icon, image, instance, component, group, section, vector
Attributes (frame): layout, justify, items, wrap, w, h, minW, maxW, p, gap, bg, fill, rounded, stroke, shadow, blur, bgblur, opacity, layoutPositioning
Attributes (grid container): cols, rows, gap, rowGap, colGap, colSizes, rowSizes, autoFlow, autoRows — see "Grid layout" below
Attributes (grid child): colSpan, rowSpan, alignX, alignY, rowStart, colStart
Attributes (text): size, weight, lineHeight, font, fill, w (w="fill" for wrap), maxLines, textTruncation, paragraphSpacing
Multi-paragraph text: put a BLANK LINE between paragraphs inside <text> — it becomes a real paragraph break. A single newline is collapsed to a space (prose wrap); for a hard single-line break write {'\n'}. Pair multi-paragraph body copy with paragraphSpacing={N} for gap between paragraphs.
Truncation: maxLines={N} caps a wrapping text at N lines with an ellipsis (needs a bounded width, e.g. w="fill" or w={240}).
Advanced text — inline emphasis spans (**bold** *italic* ~~strike~~ in content), coloured/sized spans, vertical trim, hanging indent/punctuation, list spacing → read skill:typography (don't inline these here).
Effects: shadow="0,8,32,0,#0006" or shadow={shadow(0,8,32,0,'#0006')}; blur={10} for layer blur; bgblur={20} for frosted-glass/glassmorphism background blur. Multiple effects merge automatically.
Decoration in auto-layout: floating orbs/blobs/decorative shapes inside a row/column parent need layoutPositioning="absolute" so they don't get stacked into the main-axis flow.
Full-frame backgrounds: set the parent frame's bg directly. Don't add a separate <rect> backdrop. Supported gradient strings (CSS-like subset, not full CSS):
- linear-gradient(<angle>deg, <#hex> <pos>%, ...) e.g. "linear-gradient(135deg, #A 0%, #B 100%)"
- linear-gradient(to <direction>, <stops>) directions: top/right/bottom/left and corners (e.g. "to bottom right")
- radial-gradient(<stops>) centered, ellipse-fill — no position/shape modifiers
- radial-gradient(circle, <stops>) centered, circle shape only
- conic-gradient(from <angle>deg, <stops>)
Unsupported (will be rejected): "circle at X% Y%", "ellipse at ...", "X% Y%" position syntax, named colors (red/blue/...), hsl(), and numeric stops without a percent sign.
Text: <text size={24}>content here</text>
Rich text (mixed inline styles in one text node — runtime parses markup per character):
**bold** e.g. "Click **here** to continue"
*italic* e.g. "Read the *fine print*"
***bold italic*** e.g. "This is ***critical***"
~~strikethrough~~ e.g. "Was ~~$19.99~~"
{color:#HEX|text} e.g. "{color:#EF4444|Error}: something failed"
{size:N|text} e.g. "{size:32|Big} then normal"
Nesting stacks: "{color:#EF4444|**$9.99**}" → red + bold on the same range. For uniform whole-text styling use the weight / fill / size props instead — markup is only for MIXED styles within one text node.
Instance: <instance ref="Button" variant="Size=Large"/>
Self-closing: <line w="fill" stroke="#E5E7EB"/> (use line for dividers/separators; rect/ellipse for SMALL pure decoration with no children — page-level backgrounds belong on the parent frame's bg)
Arc/Ring: <ellipse w={120} h={120} arc="0 270" fill="#4F46E5"/> (arc="start end innerRadius?" — innerRadius 0-1 makes a donut/ring)
Grid layout: `layout="grid"` + `cols={N}`. Omit `rows` for auto-rows (rows grow to fit children); set `rows={M}` for a fixed N×M. `gap`, or `rowGap`/`colGap` separately.
- Per-track sizing: `colSizes="240px 1fr 1fr"` (fixed sidebar + two flexible columns) or `rowSizes="80px 1fr"` (fixed header row + flexible body). Tokens: `Npx`/`N`=fixed px, `Nfr`=flexible share (`1fr 2fr` ⇒ 1:2), `hug`=fit-content. Setting `colSizes`/`rowSizes` infers `cols`/`rows` from the token count — don't also pass a conflicting count.
- Auto-flow: `autoFlow` packs children row-major into the next free cell (best for galleries/masonry, esp. with mixed spans). `autoRows` lets the grid manage its own row count.
- Per-child: `colSpan`/`rowSpan` (cell spanning), `alignX`/`alignY` (start|center|end|auto within the cell), `rowStart`/`colStart` (0-based explicit cell anchor — requires manual placement, so do NOT combine with `autoFlow`).
Sizing defaults (lean on them): rows are HUG (cells fit content) and the container HUGS its height automatically, while children default to fill-width / hug-height. So a content gallery needs only `cols` + a `w` (fixed px or `"fill"`) — no `rowSizes`, no height. For equal-height rows (KPI tiles, dashboards) set `rowSizes="1fr 1fr"` (children then FILL the rows); for fixed-height bands use px row sizes. A `rowSpan>1` child fills its span.
Gotchas: (1) the container can hug HEIGHT but not WIDTH (columns are FLEX) — always give it a `w` (`"fill"` or px). (2) `justify`/`items` are no-ops on a grid — use per-child `alignX`/`alignY`. (3) for a 1-column stack use `layout="column"`, not grid. Prefer grid for a 2-D matrix (galleries, KPI tiles, pricing tiers, dashboards); use row/column when children differ in width or you need space-between.
Variable binding: fill/bg/stroke accept qualified bare-name token strings (e.g. bg="$Theme/Bg/Surface"). Object literals (fill={{variable_id:...}}) drop the binding silently — always use the string form.
Swap an existing subtree: jsx({replaceId: "<id>", markup: "..."}) replaces the old node at the same parent and sibling index atomically, preserving position in one call. Markup must have a single root. Use jsx for tree creation; edit for property updates on known nodes.
Read design node(s) — choose what to surface with `facets`.
Default (no facets) returns a skeleton: id, name, type, role, children.
For anything else, list the facets you need — nothing else is included.
Facets:
structure name, type, size, layout shorthand — cheap overview
layout layoutMode/gap/padding/align/sizing (row/column, fill/hug, etc.)
paint|fill fills + Paint.boundVariables.color (see bound tokens)
stroke strokes, strokeWeight, strokeAlign, dashPattern
effects shadows, blurs
typography|text fontFamily, fontSize, fontWeight, lineHeight, letterSpacing
appearance opacity, visible, blendMode, cornerRadius, clipsContent
variables node-level boundVariables + explicitVariableModes (token bindings)
lint validation view — per-node role + visual/layout summary + issues (severity error/warning/info). Specific node only, depth-3 default; exclusive (ignores other facets).
all everything
Parameters:
node "/" for page root, or node ID from jsx/inspect results (e.g. "100:5").
facets array of facet names listed above.
depth Max tree depth (default: 5, max: 10).
Selection: when the user prompt carries a `<selected_nodes>[{id,name,type}…]</selected_nodes>` block, those IDs are authoritative (a reference, not a snapshot) — inspect them directly, don't call `get_selection`. Start shallow (depth 1–2); go deeper only when the task needs leaf access — a full-depth dump of a large tree burns thousands of tokens.
Examples:
inspect({node: "/"}) → page skeleton
inspect({node: "100:5"}) → one-node skeleton
inspect({node: "100:5", facets: ["variables"]}) → token bindings only
inspect({node: "100:5", facets: ["layout", "paint"]}) → layout + fills
inspect({node: "100:5", facets: ["all"]}) → full properties
inspect({node: "100:5", facets: ["lint"]}) → validate subtree (roles + issues)
Use `get_screenshot` for visual verification. Use `facets: ["lint"]` to validate a subtree (roles + issues) rather than read properties.
Batch update properties on nodes. The universal write tool — use for sizing, radius, opacity, effects, instance component-prop overrides, or any property not covered by focused setters.
For single-property changes, prefer focused setters (they validate input more strictly):
set_text — text content
set_fill — fill / background color
set_stroke — border
set_layout — auto-layout (gap, padding, direction)
Use when:
- Batch fixes — multiple nodes, mixed properties in one call
- Properties not covered by setters: w/h, corner, opacity, blur, shadow, INSTANCE TEXT/BOOLEAN/INSTANCE_SWAP overrides
- Need to mix Figma props and component property overrides in the same node entry
Returns (per node): `applied: [keys]` (written), `noop: [keys]` (already that value), `rejected: [{key, reason}]`. Empty arrays are omitted.
- Single: { data: { id, name, type, applied?, noop?, rejected? } }
- Batch: { data: { count, results: [...], errors?, partial? } }
`rejected` is TERMINAL: the property is invalid for that node type (e.g. `corner`/cornerRadius on a TEXT node, `layoutMode` on a vector) or readonly — it will NEVER apply. Do NOT retry a rejected property; choose one valid for that node type, or target a different node. If EVERY requested property is rejected, the call returns `{ error }` — do not re-issue it.
`partial: true` (batch) means some entries succeeded and some failed — check `errors[]` for which entries to retry.
Skip when:
- One property on one node — use the focused setter for clearer errors
- Color via variable token — use set_fill/set_stroke (edit silently drops bare-name tokens here)
- Need to apply auto-layout — use set_layout
Component property overrides:
For instances, use property DISPLAY NAMES (e.g. "Label") — edit resolves them to Figma's internal keys automatically. Component props mix with Figma props in the same call.
Examples:
edit({node: "1:2", props: {corner: 16, opacity: 0.8}})
edit({nodes: [
{node: "1:1", props: {w: "fill", corner: 8}}, // Figma native props
{node: "1:2", props: {opacity: 0.6}},
{node: "1:3", props: {Label: "Sign In"}} // instance TEXT prop (by display name)
]})