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.
JSON file structure
A Composition file is a JSON object with required fields:
{
"$type": "composition",
"name": "Button",
"structure": { ... },
"styles": { ... }
}
Root fields
| Field | Type | Required | Description |
|---|---|---|---|
$type | "composition" | ✅ | File type marker |
name | string | ✅ | Component name in Figma |
component | boolean | — | When false, creates plain nodes (Frame/Instance) instead of Component/ComponentSet. Default: true |
structure | object | ✅ | Node tree (component structure) |
styles | object | ✅ | Styles by class |
props | object | — | Variant axes |
componentProperties | object | — | Figma component properties |
transitions | object | — | Animation transitions |
themeBinding | object | — | Theme/variable collection binding |
$description | string | — | Description text on the root Component or ComponentSet in Figma (Assets / Inspect). Applied on Generate and Apply. Does not affect geometry or styles |
$metadata | JSON | — | Opaque tooling payload stored in node pluginData; does not affect generation or apply |
$description and $metadata
$descriptionmaps to Figma’s built-in component / component set description (the same field you can edit in the UI).$metadatais JSON-stringified and stored under the pluginData keysxlCompositionMetadataon 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:
"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.
{
"$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:
{
"$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
| Rule | Details |
|---|---|
| Default | If component is omitted or true, behavior is unchanged (Component / ComponentSet) |
| No variants | component: false is incompatible with props; the parser returns an error |
| Styles & refs | All 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:
"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
propsdetermines the order in the Figma variant name:"size=sm, state=default".
Boolean example:
"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:
"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
type | Figma Property | Description |
|---|---|---|
TEXT | Text Property | Binds to the layer's text content |
BOOLEAN | Boolean Property | Controls layer visibility (true/false) |
INSTANCE_SWAP | Instance Swap Property | Swaps the component instance |
SLOT | Slot Property | Native Figma slot |
Definition fields
| Field | Description |
|---|---|
type | Property type (see table above) |
layer | The class value of the target node in structure |
defaultValue | Default value (TEXT: string, BOOLEAN: boolean, INSTANCE_SWAP: component name) |
preferred | Array 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
{
"tag": "FRAME",
"class": "root",
"name": "Button",
"children": [...]
}
Node fields
| Field | Type | Required | Description |
|---|---|---|---|
tag | string | ✅ | Figma node type |
class | string | ✅ | Identifier for style binding |
name | string | — | Layer name in Figma (defaults to class) |
layer | string | — | Alias for name (if name is not set) |
content | string | — | Text content (TEXT only) |
children | array | — | Child nodes |
ref | object | — | Component reference (INSTANCE only) |
slot | object | — | Slot configuration (SLOT only) |
description | string | — | Node description |
Tags
| Tag | Creates in Figma | Description |
|---|---|---|
FRAME | Frame | Container, supports auto-layout and all frame properties |
TEXT | Text | Text layer |
COMPONENT | Component | Nested component (inside the main component) |
INSTANCE | Instance | Instance of an external component (via ref) |
SLOT | Slot / Frame | Native 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.
{
"tag": "FRAME",
"class": "container",
"children": [
{ "tag": "TEXT", "class": "label", "content": "Click me" }
]
}
TEXT — text layer
{
"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):
{
"tag": "COMPONENT",
"class": "badge",
"children": [
{ "tag": "TEXT", "class": "badge-text", "content": "New" }
]
}
INSTANCE — component instance
Inserts an instance of an existing component:
{
"tag": "INSTANCE",
"class": "icon",
"ref": {
"component": "IconPlus"
}
}
ref fields
| Field | Description |
|---|---|
component | Component name (required) |
key | Component key for library import (importComponentByKeyAsync) |
library | Library name (search hint) |
properties | Instance property overrides |
overrides | Child layer style overrides by class |
slots | Slot content overrides |
nested | Nested instance settings |
icon | Icon key for substitution |
Example with overrides:
{
"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):
{
"tag": "SLOT",
"class": "content-slot",
"slot": {
"default": "CardContent",
"preferred": ["CardContent", "CardMedia", "CardList"],
"key": "component_key_here"
}
}
slot field | Description |
|---|---|
default | Default component name |
preferred | List of preferred components |
key | Component key for import |
library | Library 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:
{
"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
slotand non-emptychildrenat the same time.
class — style binding
Every node in structure has a required class field. This is the identifier used to assign styles:
"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):
"styles": {
".root": { ... },
".label": { ... }
}
Both notations (root and .root) are equivalent.
Dot-path notation
For precise targeting of nested nodes, use dot-separated paths:
"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 property | CSS equivalent | Figma property | Values |
|---|---|---|---|
layoutMode / direction | flex-direction | Layout Mode | HORIZONTAL (row), VERTICAL (column), NONE, GRID |
itemSpacing / gap | gap | Item Spacing | number (px) or {token} |
counterAxisSpacing / wrapGap | row-gap | Counter Axis Spacing | number or {token} |
primaryAxisAlignItems / justifyContent | justify-content | Primary Axis Align | MIN (start), CENTER, MAX (end), SPACE_BETWEEN |
counterAxisAlignItems / alignItems | align-items | Counter Axis Align | MIN (start), CENTER, MAX (end), BASELINE |
counterAxisAlignContent / alignContent | align-content | Counter Axis Align Content | AUTO, SPACE_BETWEEN |
layoutWrap / flexWrap | flex-wrap | Wrap | NO_WRAP (nowrap), WRAP (wrap) |
paddingTop | padding-top | Padding Top | number or {token} |
paddingRight | padding-right | Padding Right | number or {token} |
paddingBottom | padding-bottom | Padding Bottom | number or {token} |
paddingLeft | padding-left | Padding Left | number or {token} |
padding | padding | All Paddings | number, shorthand "8 16" (vert horiz) or "8 12 16 20" (top right bottom left) |
overflow | overflow | Clip Content | hidden/clip → true, visible/auto → false |
Shorthand padding:
"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 property | CSS equivalent | Figma property | Values |
|---|---|---|---|
layoutSizingHorizontal / widthType | — | Horizontal Sizing | FIXED, HUG (auto, hug), FILL (fill, stretch) |
layoutSizingVertical / heightType | — | Vertical Sizing | FIXED, HUG (auto), FILL (fill) |
layoutAlign / alignSelf | align-self | Layout Align | INHERIT, STRETCH (stretch), MIN (start), CENTER, MAX (end) |
layoutGrow / flexGrow | flex-grow | Layout Grow | number (0 or 1) |
layoutPositioning / position | position | Positioning | AUTO (relative, static), ABSOLUTE (absolute) |
Grid Layout
| JSON property | CSS equivalent | Figma property |
|---|---|---|
gridRowCount / rows | — | Grid Row Count |
gridColumnCount / columns | — | Grid Column Count |
gridRowGap / rowGap | row-gap | Grid Row Gap |
gridColumnGap / columnGap | column-gap | Grid Column Gap |
gridRowSizes / gridTemplateRows | grid-template-rows | Grid Row Sizes |
gridColumnSizes / gridTemplateColumns | grid-template-columns | Grid Column Sizes |
gridRowSpan | grid-row span | Grid Row Span |
gridColumnSpan | grid-column span | Grid Column Span |
Tip: if grid properties appear in styles, layoutMode is automatically set to GRID.
Size and position
| JSON property | CSS equivalent | Description |
|---|---|---|
width | width | Width (px number or {token}) |
height | height | Height |
minWidth | min-width | Minimum width |
maxWidth | max-width | Maximum width |
minHeight | min-height | Minimum height |
maxHeight | max-height | Maximum height |
x | left | X position (number, "auto" for centering, "50%") |
y | top | Y position |
rotation / rotate | transform: rotate() | Rotation angle (degrees) |
Absolute positioning (Insets)
| JSON property | CSS equivalent | Description |
|---|---|---|
top | top | Top offset (sets position: ABSOLUTE + constraints) |
right | right | Right offset |
bottom | bottom | Bottom offset |
left | left | Left offset |
When using insets, the plugin automatically sets layoutPositioning: "ABSOLUTE" and calculates constraints.
Constraints
| JSON property | Figma property | Values |
|---|---|---|
constraintH / constraintHorizontal | Horizontal Constraint | MIN (start, left), CENTER, MAX (end, right), STRETCH, SCALE |
constraintV / constraintVertical | Vertical Constraint | MIN (start, top), CENTER, MAX (end, bottom), STRETCH, SCALE |
Fills and colors
| JSON property | CSS equivalent | Description |
|---|---|---|
background | background | Frame fill (color, gradient, layer array) |
color | color | Text color (for TEXT — fill; for FRAME — inherits to text descendants) |
fill | fill | Fill for vector/shape nodes inside instances |
fills | — | Figma paint layers array directly |
opacity | opacity | Opacity (0–1) |
Fill value formats:
"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:
"background": "none"
"background": "transparent"
Stroke
| JSON property | CSS equivalent | Description |
|---|---|---|
strokes | border | Figma stroke layers array |
strokeWeight / borderWidth | border-width | Stroke weight |
strokeAlign / borderAlign | — | Position: INSIDE, OUTSIDE, CENTER |
borderColor | border-color | Stroke color |
borderStyle | border-style | Line style (dash pattern) |
strokeTopWeight | border-top-width | Top stroke weight |
strokeRightWeight | border-right-width | Right |
strokeBottomWeight | border-bottom-width | Bottom |
strokeLeftWeight | border-left-width | Left |
strokesIncludedInLayout / boxSizing | box-sizing | true/"border-box" → strokes included in size |
Corner radius
| JSON property | CSS equivalent | Description |
|---|---|---|
cornerRadius / borderRadius | border-radius | All corners |
topLeftRadius | border-top-left-radius | Top left |
topRightRadius | border-top-right-radius | Top right |
bottomRightRadius | border-bottom-right-radius | Bottom right |
bottomLeftRadius | border-bottom-left-radius | Bottom left |
cornerSmoothing / cornerSmooth | — | iOS-style smoothing (0–1) |
Shorthand borderRadius:
"borderRadius": 8
"borderRadius": "8 16"
"borderRadius": "8 12 16 20"
Effects
| JSON property | CSS equivalent | Description |
|---|---|---|
effects | filter | Effects token or DTCG layer array |
boxShadow | box-shadow | Shadow token, object, or CSS string |
backgroundBlur | backdrop-filter: blur() | Background blur (number or {token}) |
layerBlur | filter: blur() | Layer blur |
glass | — | Figma Glass effect object |
none for effects:
"effects": "none"
"boxShadow": "none"
"backgroundBlur": "none"
Typography
| JSON property | CSS equivalent | Description |
|---|---|---|
fontFamily | font-family | Font name |
fontSize | font-size | Font size |
fontWeight | font-weight | Weight (number or string) |
fontStyle | font-style | normal, italic |
lineHeight | line-height | Line height (number, "AUTO", "150%") |
letterSpacing | letter-spacing | Letter spacing |
textCase | text-transform | UPPER, LOWER, TITLE, ORIGINAL; CSS: uppercase, lowercase, capitalize, none |
textDecoration | text-decoration | NONE, UNDERLINE, STRIKETHROUGH; CSS: none, underline, line-through |
paragraphSpacing | — | Paragraph spacing |
paragraphIndent | text-indent | First line indent |
textAutoResize / textSizing | — | NONE (fixed), HEIGHT (height-auto), WIDTH_AND_HEIGHT (auto), TRUNCATE |
textAlignHorizontal / textAlign | text-align | LEFT, CENTER, RIGHT, JUSTIFIED |
textAlignVertical / verticalAlign | vertical-align | TOP, CENTER, BOTTOM |
textTruncation | text-overflow | DISABLED, ENDING (ellipsis) |
typography / font | font | Typography token ({typography.body}) — expands all sub-properties |
leadingTrim | — | Vertical trim |
Tokens in values
Any style value can reference a token via {path}:
"styles": {
"root": {
"background": "{color.surface.primary}",
"cornerRadius": "{borderRadius.md}",
"itemSpacing": "{spacing.sm}"
},
"label": {
"typography": "{typography.body}",
"color": "{color.text.primary}"
}
}
Special properties
| JSON property | Description |
|---|---|
display: "none" | Node is not created during generation. Use in conditional styles to hide nodes in specific variants |
component | String — swaps the main component of an INSTANCE |
instanceProperties / properties | Instance property overrides object |
nestedInstanceProperties | Nested instance property overrides |
itemReverseZIndex / canvasStacking | true / "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
"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:
"$size=sm $state=hover .root": {
"background": "{color.brand.primary-hover}"
}
Nested syntax
"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:
- Base styles apply first (
root,label). - Matching conditional styles apply on top.
- More specific selectors (more conditions) take priority.
Full example: button
{
"$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.
Generate vs Apply
| Mode | When | What it does |
|---|---|---|
| Generate | Component not found in the file | Creates a new Component / Component Set |
| Apply | Component with this name already exists | Updates 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
| Property | none value | Result |
|---|---|---|
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:
- In Figma, select a
Frame,Component,ComponentSet, orInstance. - Open the plugin → Tokens → your composition file → Code.
- Click Get Code — the JSON with all properties (
type,name,props,componentProperty,structure,styles,transitions,$description) is dropped into the editor. - 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/leftfields (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.clientStorageand 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 withdiff-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>.jsonfile 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.
Related pages
- Overview: Tokens overview
- Types: All token types
- JSON format: Token JSON format
- Styles: Color, Gradient, Image, Fill, Opacity