Tokens

Composition

Complete guide to Composition: building components from JSON, structure, tags, properties, styles, slots, variants, and Figma generation.

What is Composition

Every Figma component is a tree of objects with specific properties: frames, text, instances, auto-layout, fills, padding. Figma internally stores all of this as structured data. Composition lets you describe that same structure in JSON and automatically build the component in Figma.

Why it matters

Picture a common scenario: a designer creates a button in Figma. A developer recreates it in code. Then the designer updates the padding. The developer updates code. The designer adds a variant. The developer adds it. Six months later, the Figma component and the code component are two different objects.

Composition solves this: the single source of truth is a JSON file. The Figma component is generated from it. Frontend code is also generated from it (via a transformer). The JSON doesn't "describe" the component — the JSON is the component.

What you get

  • Automatic component assembly — describe the structure in JSON, click Generate — the component with all variants is ready in Figma.
  • Single source of truth — truth lives in JSON, not "in the designer's head" or "somewhere in Figma."
  • Design-code sync — change the JSON, update both Figma and frontend in one operation.
  • Variants from data — define axes (size: sm/md/lg, state: default/hover) — all variant combinations are generated automatically.
  • Component property control — text properties, boolean properties, instance swap — all defined in JSON.
  • Slots — native Figma slots, created from JSON.
  • Conditional styles — different styles for different variants via selectors.
  • Incremental updates — on re-Apply, the plugin updates existing nodes rather than recreating the component.

Key idea: Figma by nature stores components as JSON trees. Composition is the natural path: you describe the component in the same language Figma thinks in.

Generating a component with variants from JSON

JSON file structure

A Composition file is a JSON object with required fields:

JSON
{
  "$type": "composition",
  "name": "Button",
  "structure": { ... },
  "styles": { ... }
}

Root fields

FieldTypeRequiredDescription
$type"composition"File type marker
namestringComponent name in Figma
componentbooleanWhen false, creates plain nodes (Frame/Instance) instead of Component/ComponentSet. Default: true
structureobjectNode tree (component structure)
stylesobjectStyles by class
propsobjectVariant axes
componentPropertiesobjectFigma component properties
transitionsobjectAnimation transitions
themeBindingobjectTheme/variable collection binding
$descriptionstringDescription text on the root Component or ComponentSet in Figma (Assets / Inspect). Applied on Generate and Apply. Does not affect geometry or styles
$metadataJSONOpaque tooling payload stored in node pluginData; does not affect generation or apply

$description and $metadata

  • $description maps to Figma’s built-in component / component set description (the same field you can edit in the UI).
  • $metadata is JSON-stringified and stored under the pluginData key sxlCompositionMetadata on the root Component or ComponentSet (e.g. schema version, doc links). It is not part of the structural composition drift hash.

name — component name

A string defining the component name in Figma:

JSON
"name": "Button"

The name appears in the Assets panel and the layers tree. If a component with this name already exists, Apply updates it; Generate creates a new one.

Important: the name must be a non-empty string. An empty name causes a parser error.


component — output mode

By default every composition generates a Component (or ComponentSet when props are defined). Setting "component": false tells the generator to create plain Figma nodes (Frame, Instance) instead.

JSON
{
  "$type": "composition",
  "name": "ProductCard",
  "component": false,
  "structure": {
    "tag": "FRAME",
    "class": "wrapper",
    "children": [
      {
        "tag": "INSTANCE",
        "class": "card",
        "ref": { "component": "Card" }
      }
    ]
  },
  "styles": {
    ".wrapper": { "layoutMode": "VERTICAL", "padding": "16", "itemSpacing": "12" }
  }
}

The result is a regular Frame with an Instance inside — not a component, not a component set.

Root Instance

When structure.tag is "INSTANCE", the plugin inserts a configured instance directly onto the page:

JSON
{
  "$type": "composition",
  "name": "SegmentPanel",
  "component": false,
  "structure": {
    "tag": "INSTANCE",
    "class": "segment",
    "ref": {
      "component": "WSegmentControl",
      "properties": { "size": "sm" },
      "slots": {
        "item-group": {
          "op": "replace",
          "nodes": [
            { "component": "WSegmentControlItem", "properties": { "label": "Tab A" } },
            { "component": "WSegmentControlItem", "properties": { "label": "Tab B" } }
          ]
        }
      }
    }
  },
  "styles": {
    ".segment": { "width": 320 }
  }
}

Rules

RuleDetails
DefaultIf component is omitted or true, behavior is unchanged (Component / ComponentSet)
No variantscomponent: false is incompatible with props; the parser returns an error
Styles & refsAll existing style properties, ref, slot, children work the same way

props — variant axes

props defines the component's variant axes. Each axis is an array of allowed values:

JSON
"props": {
  "size": ["sm", "md", "lg"],
  "state": ["default", "hover", "disabled"]
}

The plugin generates all combinations: size=sm, state=default / size=sm, state=hover / ... — 9 variants total.

Rules:

  • Values: string, number, boolean.
  • The array must be non-empty.
  • The value "default" is used as fallback in the style cascade. If there's no "default", the first element is used.
  • Key order in props determines the order in the Figma variant name: "size=sm, state=default".

Boolean example:

JSON
"props": {
  "hasIcon": [true, false],
  "size": ["sm", "md", "lg"]
}

Warning: many axes with many values creates exponentially growing variant counts. 4 axes × 4 values = 256 variants. Use a reasonable number.


componentProperties — Figma component properties

Defines native Component Properties in Figma:

JSON
"componentProperties": {
  "label": {
    "type": "TEXT",
    "layer": "label",
    "defaultValue": "Button"
  },
  "showIcon": {
    "type": "BOOLEAN",
    "layer": "icon",
    "defaultValue": true
  },
  "iconSource": {
    "type": "INSTANCE_SWAP",
    "layer": "icon",
    "defaultValue": "IconPlus",
    "preferred": ["IconPlus", "IconMinus", "IconCheck"]
  }
}

Component property types

typeFigma PropertyDescription
TEXTText PropertyBinds to the layer's text content
BOOLEANBoolean PropertyControls layer visibility (true/false)
INSTANCE_SWAPInstance Swap PropertySwaps the component instance
SLOTSlot PropertyNative Figma slot

Definition fields

FieldDescription
typeProperty type (see table above)
layerThe class value of the target node in structure
defaultValueDefault value (TEXT: string, BOOLEAN: boolean, INSTANCE_SWAP: component name)
preferredArray of component names for preferred values (INSTANCE_SWAP and SLOT)

Important: layer must exactly match the class of the target node in structure. If no node with that class is found, the property won't be bound.


structure — node tree

The most important field: describes the component structure as a tree.

Node format

JSON
{
  "tag": "FRAME",
  "class": "root",
  "name": "Button",
  "children": [...]
}

Node fields

FieldTypeRequiredDescription
tagstringFigma node type
classstringIdentifier for style binding
namestringLayer name in Figma (defaults to class)
layerstringAlias for name (if name is not set)
contentstringText content (TEXT only)
childrenarrayChild nodes
refobjectComponent reference (INSTANCE only)
slotobjectSlot configuration (SLOT only)
descriptionstringNode description

Tags

TagCreates in FigmaDescription
FRAMEFrameContainer, supports auto-layout and all frame properties
TEXTTextText layer
COMPONENTComponentNested component (inside the main component)
INSTANCEInstanceInstance of an external component (via ref)
SLOTSlot / FrameNative Figma slot when generating a component if one of the SLOT scenarios below applies; otherwise a plain Frame container

FRAME — container

The primary building block. Supports auto-layout, grid, nested children.

JSON
{
  "tag": "FRAME",
  "class": "container",
  "children": [
    { "tag": "TEXT", "class": "label", "content": "Click me" }
  ]
}

TEXT — text layer

JSON
{
  "tag": "TEXT",
  "class": "label",
  "content": "Button Label"
}

content sets the text. If a TEXT componentProperty is defined with layer: "label", the content becomes editable.

COMPONENT — nested component

Creates a nested component (not an instance, but the component itself):

JSON
{
  "tag": "COMPONENT",
  "class": "badge",
  "children": [
    { "tag": "TEXT", "class": "badge-text", "content": "New" }
  ]
}

INSTANCE — component instance

Inserts an instance of an existing component:

JSON
{
  "tag": "INSTANCE",
  "class": "icon",
  "ref": {
    "component": "IconPlus"
  }
}

ref fields

FieldDescription
componentComponent name (required)
keyComponent key for library import (importComponentByKeyAsync)
libraryLibrary name (search hint)
propertiesInstance property overrides
overridesChild layer style overrides by class
slotsSlot content overrides
nestedNested instance settings
iconIcon key for substitution

Example with overrides:

JSON
{
  "tag": "INSTANCE",
  "class": "card",
  "ref": {
    "component": "Card",
    "key": "abc123def456",
    "properties": {
      "Title": "Custom Title",
      "ShowBadge": true
    },
    "overrides": {
      "subtitle": {
        "content": "Updated subtitle"
      }
    }
  }
}

If the component isn't found: the plugin creates a placeholder — an empty 24×24 frame with the class name. This lets you generate the structure even if not all components exist yet.

SLOT

Supported scenarios (see also Composition drift):

1. Native slot + default swap — the node has a slot object (and no non-empty children; see restriction below):

JSON
{
  "tag": "SLOT",
  "class": "content-slot",
  "slot": {
    "default": "CardContent",
    "preferred": ["CardContent", "CardMedia", "CardList"],
    "key": "component_key_here"
  }
}
slot fieldDescription
defaultDefault component name
preferredList of preferred components
keyComponent key for import
libraryLibrary name

You may optionally add ref on the same node (reference mode template for the default instance in the slot).

2. Shorthand — exactly one child INSTANCE with ref, and no slot key: the plugin creates a native slot and uses that ref as the default swap (handy for simple componentProperties of type SLOT).

3. Primitive template — exactly one child that is not an INSTANCE (often a FRAME row template), and no slot: when generating the main component, the plugin creates a native slot and builds that nested structure inside. A SLOT component property whose layer matches the node class stays bound correctly. No separate default instance swap is taken from JSON.

4. Container (Frame)multiple children, or any case where a native slot cannot be used: the plugin creates a normal Frame with children (safe mode; use when several instances share one area).

Example with multiple instances:

JSON
{
  "tag": "SLOT",
  "class": "actions",
  "children": [
    {
      "tag": "INSTANCE",
      "class": "primary-action",
      "ref": { "component": "Button" }
    },
    {
      "tag": "INSTANCE",
      "class": "secondary-action",
      "ref": { "component": "ButtonGhost" }
    }
  ]
}

You cannot set both slot and non-empty children at the same time.


class — style binding

Every node in structure has a required class field. This is the identifier used to assign styles:

JSON
"structure": {
  "tag": "FRAME",
  "class": "root",
  "children": [
    { "tag": "TEXT", "class": "label" }
  ]
},
"styles": {
  "root": {
    "layoutMode": "HORIZONTAL",
    "itemSpacing": 8,
    "paddingLeft": 16,
    "paddingRight": 16
  },
  "label": {
    "fontSize": 14,
    "fontWeight": 500
  }
}

Dot prefix notation

Class names can also be written with a dot prefix (CSS-like syntax):

JSON
"styles": {
  ".root": { ... },
  ".label": { ... }
}

Both notations (root and .root) are equivalent.

Dot-path notation

For precise targeting of nested nodes, use dot-separated paths:

JSON
"styles": {
  "root": { ... },
  "root.label": {
    "fontSize": 16
  }
}

root.label applies to the node with class: "label" that is a descendant of the node with class: "root".


styles — style properties

The most extensive section. Styles describe the visual presentation of each node.

Layout (Auto Layout)

JSON propertyCSS equivalentFigma propertyValues
layoutMode / directionflex-directionLayout ModeHORIZONTAL (row), VERTICAL (column), NONE, GRID
itemSpacing / gapgapItem Spacingnumber (px) or {token}
counterAxisSpacing / wrapGaprow-gapCounter Axis Spacingnumber or {token}
primaryAxisAlignItems / justifyContentjustify-contentPrimary Axis AlignMIN (start), CENTER, MAX (end), SPACE_BETWEEN
counterAxisAlignItems / alignItemsalign-itemsCounter Axis AlignMIN (start), CENTER, MAX (end), BASELINE
counterAxisAlignContent / alignContentalign-contentCounter Axis Align ContentAUTO, SPACE_BETWEEN
layoutWrap / flexWrapflex-wrapWrapNO_WRAP (nowrap), WRAP (wrap)
paddingToppadding-topPadding Topnumber or {token}
paddingRightpadding-rightPadding Rightnumber or {token}
paddingBottompadding-bottomPadding Bottomnumber or {token}
paddingLeftpadding-leftPadding Leftnumber or {token}
paddingpaddingAll Paddingsnumber, shorthand "8 16" (vert horiz) or "8 12 16 20" (top right bottom left)
overflowoverflowClip Contenthidden/clip → true, visible/auto → false

Shorthand padding:

JSON
"padding": 16           // all sides 16
"padding": "8 16"       // vert=8, horiz=16
"padding": "8 12 16 20" // top=8, right=12, bottom=16, left=20

Child sizing

JSON propertyCSS equivalentFigma propertyValues
layoutSizingHorizontal / widthTypeHorizontal SizingFIXED, HUG (auto, hug), FILL (fill, stretch)
layoutSizingVertical / heightTypeVertical SizingFIXED, HUG (auto), FILL (fill)
layoutAlign / alignSelfalign-selfLayout AlignINHERIT, STRETCH (stretch), MIN (start), CENTER, MAX (end)
layoutGrow / flexGrowflex-growLayout Grownumber (0 or 1)
layoutPositioning / positionpositionPositioningAUTO (relative, static), ABSOLUTE (absolute)

Grid Layout

JSON propertyCSS equivalentFigma property
gridRowCount / rowsGrid Row Count
gridColumnCount / columnsGrid Column Count
gridRowGap / rowGaprow-gapGrid Row Gap
gridColumnGap / columnGapcolumn-gapGrid Column Gap
gridRowSizes / gridTemplateRowsgrid-template-rowsGrid Row Sizes
gridColumnSizes / gridTemplateColumnsgrid-template-columnsGrid Column Sizes
gridRowSpangrid-row spanGrid Row Span
gridColumnSpangrid-column spanGrid Column Span

Tip: if grid properties appear in styles, layoutMode is automatically set to GRID.

Size and position

JSON propertyCSS equivalentDescription
widthwidthWidth (px number or {token})
heightheightHeight
minWidthmin-widthMinimum width
maxWidthmax-widthMaximum width
minHeightmin-heightMinimum height
maxHeightmax-heightMaximum height
xleftX position (number, "auto" for centering, "50%")
ytopY position
rotation / rotatetransform: rotate()Rotation angle (degrees)

Absolute positioning (Insets)

JSON propertyCSS equivalentDescription
toptopTop offset (sets position: ABSOLUTE + constraints)
rightrightRight offset
bottombottomBottom offset
leftleftLeft offset

When using insets, the plugin automatically sets layoutPositioning: "ABSOLUTE" and calculates constraints.

Constraints

JSON propertyFigma propertyValues
constraintH / constraintHorizontalHorizontal ConstraintMIN (start, left), CENTER, MAX (end, right), STRETCH, SCALE
constraintV / constraintVerticalVertical ConstraintMIN (start, top), CENTER, MAX (end, bottom), STRETCH, SCALE

Fills and colors

JSON propertyCSS equivalentDescription
backgroundbackgroundFrame fill (color, gradient, layer array)
colorcolorText color (for TEXT — fill; for FRAME — inherits to text descendants)
fillfillFill for vector/shape nodes inside instances
fillsFigma paint layers array directly
opacityopacityOpacity (0–1)

Fill value formats:

JSON
"background": "#6366F1"
"background": "rgba(99, 102, 241, 0.5)"
"background": "linear-gradient(135deg, #6366F1 0%, #EC4899 100%)"
"background": "{color.brand.primary}"
"background": [
  { "type": "SOLID", "color": "#FFFFFF", "opacity": 0.9 },
  { "type": "GRADIENT_LINEAR", "gradientStops": [...] }
]

none and transparent:

JSON
"background": "none"
"background": "transparent"

Stroke

JSON propertyCSS equivalentDescription
strokesborderFigma stroke layers array
strokeWeight / borderWidthborder-widthStroke weight
strokeAlign / borderAlignPosition: INSIDE, OUTSIDE, CENTER
borderColorborder-colorStroke color
borderStyleborder-styleLine style (dash pattern)
strokeTopWeightborder-top-widthTop stroke weight
strokeRightWeightborder-right-widthRight
strokeBottomWeightborder-bottom-widthBottom
strokeLeftWeightborder-left-widthLeft
strokesIncludedInLayout / boxSizingbox-sizingtrue/"border-box" → strokes included in size

Corner radius

JSON propertyCSS equivalentDescription
cornerRadius / borderRadiusborder-radiusAll corners
topLeftRadiusborder-top-left-radiusTop left
topRightRadiusborder-top-right-radiusTop right
bottomRightRadiusborder-bottom-right-radiusBottom right
bottomLeftRadiusborder-bottom-left-radiusBottom left
cornerSmoothing / cornerSmoothiOS-style smoothing (0–1)

Shorthand borderRadius:

JSON
"borderRadius": 8
"borderRadius": "8 16"
"borderRadius": "8 12 16 20"

Effects

JSON propertyCSS equivalentDescription
effectsfilterEffects token or DTCG layer array
boxShadowbox-shadowShadow token, object, or CSS string
backgroundBlurbackdrop-filter: blur()Background blur (number or {token})
layerBlurfilter: blur()Layer blur
glassFigma Glass effect object

none for effects:

JSON
"effects": "none"
"boxShadow": "none"
"backgroundBlur": "none"

Typography

JSON propertyCSS equivalentDescription
fontFamilyfont-familyFont name
fontSizefont-sizeFont size
fontWeightfont-weightWeight (number or string)
fontStylefont-stylenormal, italic
lineHeightline-heightLine height (number, "AUTO", "150%")
letterSpacingletter-spacingLetter spacing
textCasetext-transformUPPER, LOWER, TITLE, ORIGINAL; CSS: uppercase, lowercase, capitalize, none
textDecorationtext-decorationNONE, UNDERLINE, STRIKETHROUGH; CSS: none, underline, line-through
paragraphSpacingParagraph spacing
paragraphIndenttext-indentFirst line indent
textAutoResize / textSizingNONE (fixed), HEIGHT (height-auto), WIDTH_AND_HEIGHT (auto), TRUNCATE
textAlignHorizontal / textAligntext-alignLEFT, CENTER, RIGHT, JUSTIFIED
textAlignVertical / verticalAlignvertical-alignTOP, CENTER, BOTTOM
textTruncationtext-overflowDISABLED, ENDING (ellipsis)
typography / fontfontTypography token ({typography.body}) — expands all sub-properties
leadingTrimVertical trim

Tokens in values

Any style value can reference a token via {path}:

JSON
"styles": {
  "root": {
    "background": "{color.surface.primary}",
    "cornerRadius": "{borderRadius.md}",
    "itemSpacing": "{spacing.sm}"
  },
  "label": {
    "typography": "{typography.body}",
    "color": "{color.text.primary}"
  }
}

Special properties

JSON propertyDescription
display: "none"Node is not created during generation. Use in conditional styles to hide nodes in specific variants
componentString — swaps the main component of an INSTANCE
instanceProperties / propertiesInstance property overrides object
nestedInstancePropertiesNested instance property overrides
itemReverseZIndex / canvasStackingtrue / "first-on-top" — first child is visually on top

Important: display: "flex" is not supported. To enable auto-layout, use layoutMode: "HORIZONTAL" / "VERTICAL" or aliases direction: "row" / "column".


Conditional styles (selectors)

Different styles for different variants via conditional keys in styles:

Syntax

JSON
"styles": {
  "root": { ... },
  "$size=sm .root": {
    "paddingLeft": 8,
    "paddingRight": 8
  },
  "$size=lg .root": {
    "paddingLeft": 24,
    "paddingRight": 24
  },
  "$state=disabled .root": {
    "opacity": 0.5
  }
}

Format: $prop=value .class

Combine conditions:

JSON
"$size=sm $state=hover .root": {
  "background": "{color.brand.primary-hover}"
}

Nested syntax

JSON
"styles": {
  "$state=hover": {
    ".root": {
      "background": "{color.brand.primary-hover}"
    },
    ".label": {
      "color": "{color.white}"
    }
  }
}

Both formats are equivalent.

Style cascade

When generating variant size=sm, state=hover:

  1. Base styles apply first (root, label).
  2. Matching conditional styles apply on top.
  3. More specific selectors (more conditions) take priority.

Full example: button

JSON
{
  "$type": "composition",
  "name": "Button",
  "props": {
    "size": ["md", "sm", "lg"],
    "variant": ["primary", "secondary", "ghost"],
    "state": ["default", "hover", "disabled"]
  },
  "componentProperties": {
    "label": {
      "type": "TEXT",
      "layer": "label",
      "defaultValue": "Button"
    },
    "showIcon": {
      "type": "BOOLEAN",
      "layer": "icon-wrap",
      "defaultValue": false
    }
  },
  "structure": {
    "tag": "FRAME",
    "class": "root",
    "children": [
      {
        "tag": "FRAME",
        "class": "icon-wrap",
        "children": [
          {
            "tag": "INSTANCE",
            "class": "icon",
            "ref": { "component": "IconPlus" }
          }
        ]
      },
      {
        "tag": "TEXT",
        "class": "label",
        "content": "Button"
      }
    ]
  },
  "styles": {
    "root": {
      "layoutMode": "HORIZONTAL",
      "counterAxisAlignItems": "CENTER",
      "primaryAxisAlignItems": "CENTER",
      "itemSpacing": 8,
      "paddingLeft": 16,
      "paddingRight": 16,
      "paddingTop": 10,
      "paddingBottom": 10,
      "cornerRadius": "{borderRadius.md}",
      "layoutSizingHorizontal": "HUG",
      "layoutSizingVertical": "HUG"
    },
    "icon-wrap": {
      "layoutMode": "HORIZONTAL",
      "layoutSizingHorizontal": "HUG",
      "layoutSizingVertical": "HUG"
    },
    "icon": {
      "width": 20,
      "height": 20
    },
    "label": {
      "fontFamily": "Inter",
      "fontSize": 14,
      "fontWeight": 500,
      "lineHeight": "20px",
      "textAutoResize": "WIDTH_AND_HEIGHT"
    },

    "$variant=primary .root": {
      "background": "{color.brand.primary}"
    },
    "$variant=primary .label": {
      "color": "#FFFFFF"
    },
    "$variant=secondary .root": {
      "background": "{color.surface.secondary}",
      "strokeWeight": 1,
      "borderColor": "{color.border.default}"
    },
    "$variant=secondary .label": {
      "color": "{color.text.primary}"
    },
    "$variant=ghost .root": {
      "background": "none"
    },
    "$variant=ghost .label": {
      "color": "{color.brand.primary}"
    },

    "$size=sm .root": {
      "paddingLeft": 12,
      "paddingRight": 12,
      "paddingTop": 6,
      "paddingBottom": 6,
      "itemSpacing": 6
    },
    "$size=sm .label": {
      "fontSize": 12,
      "lineHeight": "16px"
    },
    "$size=sm .icon": {
      "width": 16,
      "height": 16
    },
    "$size=lg .root": {
      "paddingLeft": 24,
      "paddingRight": 24,
      "paddingTop": 14,
      "paddingBottom": 14,
      "itemSpacing": 10
    },
    "$size=lg .label": {
      "fontSize": 16,
      "lineHeight": "24px"
    },
    "$size=lg .icon": {
      "width": 24,
      "height": 24
    },

    "$state=hover $variant=primary .root": {
      "background": "{color.brand.primary-hover}"
    },
    "$state=disabled .root": {
      "opacity": 0.5
    }
  }
}

This JSON generates a Component Set Button with 27 variants (3 × 3 × 3), each with its own styles.

Generating a button with 27 variants

Generate vs Apply

ModeWhenWhat it does
GenerateComponent not found in the fileCreates a new Component / Component Set
ApplyComponent with this name already existsUpdates styles, structure, and properties of the existing component

During Apply:

  • The plugin matches existing nodes by class (binding).
  • Compatible nodes are updated (styles, properties).
  • New nodes from JSON are added.
  • Nodes missing from JSON are removed.
  • The component is not recreated — it's an incremental update.

Tip: use Apply for iterative work. Change JSON → Apply → see the result instantly, without losing bindings.


Handling none

Propertynone valueResult
display"none"Node is not created (or removed on Apply)
background"none"All fills removed
color"none"Text fill removed
fill"none"Shape fill removed
opacity"none"Reset to 1
effects"none"All effects removed
boxShadow"none"Shadows removed
backgroundBlur"none"Blur removed
layerBlur"none"Blur removed
glass"none"Glass effect removed
padding"none"All paddings = 0

Media files

Screenshots and videos for this section:

Site/public/docs-media/tokens/
├── composition-generate-demo.mp4
├── composition-button-variants.mp4
├── composition-structure-tree.png
├── composition-conditional-styles.png
└── composition-slots-demo.mp4

Composition editor: Get Code and Grid Settings

The composition-token editor header has two extra buttons to the right of Save that speed up component work.

Get Code — import JSON from a Figma selection

Get Code (available in Code mode only) grabs the full JSON of the currently selected Figma node and injects it into the editor.

How to use:

  1. In Figma, select a Frame, Component, ComponentSet, or Instance.
  2. Open the plugin → Tokens → your composition file → Code.
  3. Click Get Code — the JSON with all properties (type, name, props, componentProperty, structure, styles, transitions, $description) is dropped into the editor.
  4. Save the file and, if needed, run generation right away.

The button is disabled when nothing is selected, the selection is an unsupported node type, or the multi-selection set cannot be normalized into a synthetic ComponentSet (see below).

This replaces the old flow: Dev Mode → Inspect → Code → JSON → copy → back to Design → paste into composition.

Directly-bound variables (Get Code and CodeGen)

When Get Code (and CodeGen for Vue / SwiftUI / Kotlin) sees a variable binding on a node, the emitted token reference is the variable the designer bound — Figma alias chains are not collapsed to the root collection by default. That keeps semantic tokens (for example theme-level names) stable in JSON and in generated code.

Exception — decomposed styles. When a Text or Effect style is expanded into primitives, internal wrapper variables may still be resolved to leaf values; that path is documented for tooling, not for end-user token names.

Multi-select: synthetic ComponentSet

If two or more frames / components / instances are selected (for example default and hover buttons), the plugin can build a synthetic ComponentSet in memory: one composition JSON with those nodes as variants on a shared axis. The Figma document is not modified.

Smart name parsing supports common naming patterns (state=default, variant=primary, Figma ComponentSet names, shared prefixes, etc.). The output JSON includes "$synthetic": true and variant-scoped style selectors.

The Get Code label switches to Get Code (ComponentSet × N) when multi-select is valid. If any selected node is incompatible (for example bare TEXT / RECTANGLE), the plugin falls back to single-node mode using the first supported node.

Grid Settings — ComponentSet grid configuration

Grid (available in both Code and Visual modes) opens a modal that configures the pink ComponentSet wrapper the plugin creates when generating variants. Settings apply to the next generation; they are not part of the composition JSON.

You can configure:

  • Appearance — stroke color, width, style (solid, dashed, dotted, double, groove, ridge, inset, outset, none), background color, corner radius. Values accept CSS-like shorthand (e.g. 2px dashed #fe5e01).
  • Layout — spacing between props (propGap), between values (valueGap), and stacking axis (horizontal / vertical). Prop order is read from the composition JSON. For nested layouts with Tier columns, you can set per-level horizontal gaps between sibling branches (fallback: propGap).
  • Padding — four independent top / right / bottom / left fields (in px, default 24). This is the distance from variants to the pink wrapper edges. Annotations (when enabled) live inside this padding area, so make padding large enough on the sides where labels are needed (typically ≥ 32–48 px).
  • Annotations — toggles for prop-name and value-name labels. Labels render as a GroupNode sibling next to the ComponentSet: groups don't display their name above the ComponentSet and don't carry their own stroke/fill, so they don't clutter the canvas. Coordinates are anchored to the ComponentSet — when the generated component is moved to the cursor, the label group moves with it. With Tier columns and multiple section axes, optional controls adjust multi-line column headers: Custom px indent per tree level and Extra line spacing (px) between lines.
  • Preview — a schematic SVG inside the modal shows where variants, labels and padding will land.

Actions:

  • Apply — stores settings locally via figma.clientStorage and closes the modal. Next generation will pick them up.
  • Save Pattern — opens a nested dialog with a name input and Cancel / Save buttons. The pattern is written into diff-grid.<fileKey>.json — a system file synced through Git on par with diff-id / diff-code-connect (covered by Pull, Push and Status). Patterns are shared across the team.
  • Pattern picker — selecting a saved pattern loads its values into the modal fields; press Apply or Save Pattern (overwrites the pattern with the same name).
  • Delete pattern — available from the pattern dropdown; deleting the last pattern clears the diff-grid.<fileKey>.json file entirely.

Grid Settings are plugin-side rendering parameters for the ComponentSet wrapper, not part of the composition JSON. They do not touch diff-id, do not alter tokens, and do not change the inner structure of generated variants.