Tokens

Composition

Complete Composition reference for SXL Studio: schema, structure, slots, component properties, styles mapping, transitions, theme binding, and production workflows.

Overview

$type: "composition" is the SXL Studio contract for describing a Figma component as JSON.

It stores:

  • component structure (FRAME, TEXT, COMPONENT, INSTANCE, SLOT, RECTANGLE, ELLIPSE, LINE);
  • variant axes (props);
  • layer styling (styles);
  • component-level controls (componentProperties);
  • optional behavior (transitions, themeBinding).

In practice, Composition is the bridge between design intent and repeatable generation/update in Figma.


How And Where To Use It

Use Composition when you need predictable, repeatable component building in a design system.

Common use cases:

  • generate new components/component sets from a token file;
  • update existing components without rebuilding from scratch (Apply flow);
  • keep variant structure, slot content, and styling rules in one source;
  • hand off a deterministic component model to engineers and agents.

How SXL Studio works with it

  1. You create/edit JSON manually or bootstrap from Get Code.
  2. SXL Studio parses and validates the file.
  3. Generate creates a new component/component set, or Apply updates tracked nodes.
  4. Tracking is stored in diff-id.json to keep updates stable between runs.

React / Vue 3 transformation

Composition JSON is designed to be machine-readable and stable enough for custom transformation scripts.

  • The plugin itself uses Composition for Figma generation/update.
  • For code output (for example Vue 3), you can use a separate script/agent pipeline that reads structure + styles and maps them to your framework.
  • In Dev Mode, SXL Studio also has dedicated codegen outputs (including Vue 3) that can be used as reference output for such pipelines.

Pre-Check Before Generate / Apply

Before running generation/update, fix JSON editor errors first.

Errors are typically caused by:

  • unresolved token references ({path.to.token});
  • invalid alias syntax;
  • schema violations (for example invalid SLOT shape).

Recommended flow:

  1. Open JSON in editor.
  2. Resolve all validation issues.
  3. Save.
  4. Run Generate or Apply.

Root Fields

FieldTypeRequiredNotes
$type"composition"File type marker
namestringComponent / component set name
$descriptionstringFigma description on root component/set
$metadataunknown JSONTooling metadata, does not affect rendering
componentbooleanDefault true; false generates plain nodes
propsRecord<string, (string | boolean | number)[]>Variant axes
structureCompositionStructureNodeLayer tree
stylesRecord<string, object>Base styles + selector rules
componentPropertiesRecord<string, object>Figma component properties
transitionsRecord<string, TransitionDef>Prototype transitions
transitionRecord<string, TransitionDef>Legacy alias accepted on parse
themeBindingThemeBindingVariant-prop to variable-mode mapping
sizeobjectOptional embedded size token block
styleobjectOptional embedded style token block
slotHostPipelineobjectAdvanced host/slot pipeline spec
segmentIconPostPassRefMarkersstring[]Advanced icon sync markers
$figma.slotHostPipelineobjectAlternative location for slotHostPipeline
$figma.segmentIconPostPassRefMarkersstring[]Alternative location for icon markers
selectorsauto-generatedautoGenerated by parser from styles keys; do not author manually

Important constraints

  • component: false is incompatible with non-empty props.
  • Removed legacy keys produce parse errors:
    • adapters;
    • sizeStyles;
    • colorStyles.

props (Variant Axes)

props defines all allowed values for each axis.

JSON
"props": {
  "size": ["sm", "md", "lg"],
  "state": ["default", "hover", "active"],
  "compact": [true, false]
}

Behavior:

  • every combination is generated as a variant;
  • selector matching compares values as strings;
  • if an axis has default, it is used as selector fallback for missing value-specific rules;
  • without default, fallback uses the first value.

Recommendation: keep axis count intentional. Variant combinations grow multiplicatively.


componentProperties

Defines native Figma properties exposed on component instances.

JSON
"componentProperties": {
  "label": { "type": "TEXT", "layer": "label", "defaultValue": "Button" },
  "showIcon": { "type": "BOOLEAN", "layer": "icon-wrap", "defaultValue": true },
  "icon": {
    "type": "INSTANCE_SWAP",
    "layer": "icon",
    "defaultValue": "circle-info",
    "preferred": ["circle-info", "star"]
  }
}

Supported property types

Typelayer meaningdefaultValue
TEXTclass in structureRequired by schema, auto-derived from text content if omitted
BOOLEANclass in structureRequired by schema, defaults to true if omitted
INSTANCE_SWAPclass in structureRequired
SLOTclass in structureRequired
NESTED_INSTANCENested instance layer name (not class)Not required

layer: class vs real layer name

  • For TEXT, BOOLEAN, INSTANCE_SWAP, SLOT: use the structure class.
  • For NESTED_INSTANCE: use the real nested layer name in Figma.

structure

structure is the tree that the generator builds.

JSON
"structure": {
  "tag": "FRAME",
  "class": "root",
  "name": "Root",
  "children": []
}

Node fields

FieldRequiredMeaning
tagFRAME | TEXT | COMPONENT | INSTANCE | SLOT | RECTANGLE | ELLIPSE | LINE
classStyle/target identifier
nameExplicit Figma layer name
layerAlias for name when name is omitted
contentText content (mainly for TEXT)
descriptionHuman note
refComponent reference object
slotNative slot config for SLOT reference mode
childrenChild nodes

class and name: practical rules

  • class is required and is the primary style binding key.
  • name controls actual Figma layer naming.
  • For nested targeting (ref.nested, NESTED_INSTANCE), stable layer names are critical.
  • If nested targeting is required, explicitly set name and keep it stable over time.

Supported tag values

TagRuntime behavior
FRAMECreates frame/container
TEXTCreates text layer
COMPONENTCreates nested component node
INSTANCECreates instance from ref; falls back to placeholder if unresolved
SLOTCreates native slot when available, otherwise fallback behavior
RECTANGLECreates rectangle shape
ELLIPSECreates ellipse shape
LINECreates line shape

VECTOR is intentionally not accepted yet. Figma can create an empty vector node, but a useful vector requires an explicit vectorPaths / vector-network contract. Add vectors through INSTANCE refs or wait for a dedicated vector DSL.


SLOT Modes And Rules

SLOT has strict mode rules.

1) Reference mode

Use slot config, optional ref template.

JSON
{
  "tag": "SLOT",
  "class": "content-slot",
  "slot": { "default": "WButton", "preferred": ["WButton", "WChip"] },
  "ref": {
    "component": "WButton",
    "properties": { "size": "md" }
  }
}

2) Children mode

Use children, no slot.

JSON
{
  "tag": "SLOT",
  "class": "rows",
  "children": [
    { "tag": "FRAME", "class": "row", "children": [] }
  ]
}

3) Single-child shorthand

If SLOT has exactly one INSTANCE child with ref and no slot, it is treated as reference mode shorthand.

Validation constraints

  • SLOT cannot have both slot and non-empty children.
  • SLOT.ref is allowed only in reference mode.

ref For INSTANCE / SLOT

ref defines which component to instantiate and how to configure it.

JSON
"ref": {
  "component": "WNavItem",
  "library": "SXL DS",
  "key": "abc123",
  "properties": { "state": "active" }
}

ref fields

FieldMeaning
componentTarget component name (required when ref exists)
libraryLibrary hint
keyPublish key hint
propertiesPrimitive instance property values
iconBindPropertyExplicit property name for icon instance swap
iconNested icon swap descriptor
overridesChild content overrides
slotsSlot overrides on the referenced instance
nestedNested instance updates by layer name
descriptionOptional note

ref.properties

Used for direct property assignment on instance (and nested fallback when needed).

ref.icon and iconBindProperty

Use when you need deterministic icon swap behavior.

  • iconBindProperty (recommended) targets a known instance-swap property name.
  • Without it, runtime tries to find a suitable nested swap target.

ref.nested

Targets nested instances by layer name.

JSON
"nested": {
  "Badge": {
    "properties": { "label": "3" },
    "icon": { "component": "fire-3", "properties": { "style": "filled" } }
  }
}

ref.slots

Override slots inside the referenced instance.

JSON
"slots": {
  "footer": {
    "op": "replace",
    "nodes": [
      { "component": "WButton", "name": "apply", "properties": { "variant": "primary" } }
    ]
  }
}

Operations:

opEffect
replaceReplace slot content
appendAppend nodes
patchSelectively patch; supports remove

styles

styles controls layer appearance/layout and variant-specific overrides.

Authoring formats

All formats below are supported.

Legacy class key

JSON
"styles": {
  "root": { "direction": "row" }
}

CSS-like class key

JSON
"styles": {
  ".root": { "direction": "row" }
}

Variant selector key

JSON
"styles": {
  "$state=hover .root": { "background": "{color.brand.hover}" }
}

Nested selector blocks

JSON
"styles": {
  ".wrap": {
    "padding": 8,
    ".item": { "widthType": "fill" },
    "$state=active": {
      ".item": { "opacity": 1 }
    }
  }
}

The parser flattens nested selector objects into canonical selector entries.

Selector resolution rules

  • Base styles apply first.
  • Matching selectors apply by specificity (more conditions override fewer).
  • Same specificity: later declaration wins (JSON order).
  • Descendant selectors resolve through dot paths:
    • .footer .item => footer.item.

Class-path precision

When repeated class names exist in different branches, prefer full path selectors (.header .item, .footer .item) over a short .item key.


Styles In Real Code (Practical Recipes)

This section shows how to write styles in production JSON.

1) Container layout + spacing

JSON
"styles": {
  ".card": {
    "direction": "column",
    "justifyContent": "start",
    "alignItems": "stretch",
    "gap": 12,
    "padding": "16 16 20 16",
    "overflow": "hidden"
  },
  ".header": {
    "direction": "row",
    "alignItems": "center",
    "justifyContent": "space-between",
    "gap": 8
  }
}

2) Sizing + absolute insets

JSON
"styles": {
  ".card": {
    "width": 360,
    "heightType": "hug"
  },
  ".badge": {
    "position": "absolute",
    "top": 8,
    "right": 8
  },
  ".overlay": {
    "position": "absolute",
    "top": 0,
    "right": 0,
    "bottom": 0,
    "left": 0
  }
}

3) Border + radius + outline

JSON
"styles": {
  ".card": {
    "border": "1px solid #E7EAF1",
    "borderRadius": 20
  },
  "$state=active .card": {
    "outline": "2px solid #0D6EFD",
    "outlineOffset": "2px"
  }
}

4) Shape primitives, mask, and aspect ratio

JSON
"structure": {
  "tag": "FRAME",
  "class": "badge",
  "children": [
    { "tag": "RECTANGLE", "class": "bg" },
    { "tag": "ELLIPSE", "class": "avatar-mask" }
  ]
},
"styles": {
  ".badge": { "width": 120, "height": 32 },
  ".bg": { "width": "100%", "height": "100%", "background": "{color.badge.bg}" },
  ".avatar-mask": {
    "width": 24,
    "height": 24,
    "aspectRatio": "1/1",
    "mask": "alpha"
  }
}

Masking is Figma-native layer masking, not CSS mask-composite. A mask affects following siblings in the same container, so keep mask shapes and masked content in a small wrapper frame when you need predictable clipping. For true cut-outs/subtract shapes, use prepared vector/boolean components through INSTANCE.

5) Backgrounds (solid, gradient, multi-layer image)

JSON
"styles": {
  ".card": {
    "background": "#FFFFFF"
  },
  ".hero": {
    "background": "linear-gradient(135deg, #7B5CFA 0%, #FA5CB4 100%)"
  },
  ".media-slot": {
    "background": [
      {
        "type": "image",
        "url": "https://images.unsplash.com/photo-1498050108023-c5249f4df085",
        "scaleMode": "FILL",
        "opacity": 0.72,
        "blendMode": "NORMAL"
      },
      "rgba(0,0,0,0.24)"
    ]
  }
}

6) Typography

JSON
"styles": {
  ".title": {
    "fontFamily": "Inter",
    "fontWeight": 600,
    "fontSize": 18,
    "lineHeight": "24px",
    "letterSpacing": "0px",
    "textAlign": "left",
    "textSizing": "height-auto"
  },
  ".meta": {
    "fontFamily": "Inter",
    "fontWeight": 500,
    "fontSize": 12,
    "lineHeight": "16px",
    "textCase": "uppercase",
    "textDecoration": "none"
  }
}

7) Effects and shadows

JSON
"styles": {
  ".card": {
    "boxShadow": [
      { "x": 0, "y": 8, "blur": 24, "spread": 0, "color": "rgba(16,24,40,0.14)" }
    ]
  },
  "$state=hover .card": {
    "boxShadow": [
      { "x": 0, "y": 14, "blur": 36, "spread": 0, "color": "rgba(16,24,40,0.20)" }
    ]
  }
}

8) Instance controls inside styles

JSON
"styles": {
  ".action-main": {
    "component": "WButton",
    "instanceProperties": {
      "variant": "primary",
      "size": "md",
      "label": "Continue"
    }
  },
  ".badge": {
    "nestedInstanceProperties": {
      "Badge": {
        "label": "2"
      }
    }
  }
}

9) Figma-specific metadata

JSON
"styles": {
  ".root": {
    "primaryAxisSizingMode": "auto",
    "counterAxisSizingMode": "fixed",
    "layoutGrids": [
      { "pattern": "GRID", "sectionSize": 8, "color": { "r": 0, "g": 0, "b": 1, "a": 0.12 } }
    ],
    "exportSettings": [
      { "format": "PNG", "suffix": "@2x", "constraint": { "type": "SCALE", "value": 2 } }
    ],
    "explicitVariableModes": {
      "Themes": "Dark"
    }
  }
}

explicitVariableModes accepts local collection name/id -> mode name/id. Use null, false, "none", "auto", or "unset" for a collection to clear the explicit mode for that collection.

10) Multi-axis selector examples

JSON
"styles": {
  "$size=sm .card": { "width": 320 },
  "$size=lg .card": { "width": 420 },
  "$state=disabled .card": { "opacity": 0.56 },
  "$badge=off .badge": { "display": "none" },
  "$theme=dark .card": { "background": "#121723", "border": "1px solid #2A3142" },
  "$theme=dark $state=hover .card": {
    "boxShadow": [
      { "x": 0, "y": 14, "blur": 40, "spread": 0, "color": "rgba(0,0,0,0.45)" }
    ]
  }
}

JSON → Figma/CSS-Like Key Mapping

This table documents the canonical author-facing keys and their runtime targets.

Author keyRuntime key / behavior
directionlayoutMode
justifyContentprimaryAxisAlignItems
alignItemscounterAxisAlignItems
alignContentcounterAxisAlignContent
flexWraplayoutWrap
gapitemSpacing
wrapGapcounterAxisSpacing
widthTypelayoutSizingHorizontal
heightTypelayoutSizingVertical
primaryAxisSizingModeFigma auto-layout container sizing (AUTO/FIXED)
counterAxisSizingModeFigma auto-layout container sizing (AUTO/FIXED)
alignSelflayoutAlign
flexGrowlayoutGrow
positionlayoutPositioning
top / right / bottom / leftInsets (__inset*__), then applied as layout/position constraints
textSizingtextAutoResize
textAligntextAlignHorizontal
verticalAligntextAlignVertical
rotaterotation
borderRadiuscornerRadius (or corner split for shorthand)
cornerSmoothcornerSmoothing
rowsgridRowCount
columnsgridColumnCount
rowGapgridRowGap
columnGapgridColumnGap
gridTemplateRowsgridRowSizes
gridTemplateColumnsgridColumnSizes
borderWidthstrokeWeight
borderAlignstrokeAlign
borderColorstrokes synthesis
backgroundfills synthesis (none/transparent clears fills)
colortext-fill synthesis (__color__)
fillshape-fill synthesis (__shapeFills__)
bordershorthand parser -> strokes/weight/style/align
outline + outlineOffsetOUTSIDE stroke + optional outline-shadow composite
boxShadoweffects synthesis (__boxShadow__)
effectseffects composite (__effectsComposite__)
backgroundBlurbackground blur synthesis
layerBlurlayer blur synthesis
glassglass effect synthesis
typography / fonttypography composite apply
fontStylesemantic font-style merge
contenttext content write on TEXT
componentinstance main-component swap (INSTANCE only)
instanceProperties / propertiesinstance property assignment
nestedInstancePropertiesnested instance property assignment by layer name
constraintH / constraintHorizontalhorizontal constraint alias
constraintV / constraintVerticalvertical constraint alias
overflowclipsContent (hidden/clip => true, visible/auto/scroll => false)
displayvisibility control (none => hidden, non-none => visible)
aspectRatio / targetAspectRatioresize to ratio, then lockAspectRatio(); false/none unlocks
maskisMask + optional maskType (alpha, vector, luminance)
isMask / maskTypedirect Figma mask fields
layoutGridsdirect Figma layout grids array
exportSettingsdirect Figma export settings array
explicitVariableModes / variableModesexplicit variable mode assignment by collection/mode name or id

Value Mapping (Author Values → Figma Enums)

PropertyAuthor valuesRuntime enum/value
directionrow, column, none, gridHORIZONTAL, VERTICAL, NONE, GRID
justifyContentstart, center, end, space-betweenMIN, CENTER, MAX, SPACE_BETWEEN
alignItemsstart, center, end, baselineMIN, CENTER, MAX, BASELINE
alignContentauto, space-betweenAUTO, SPACE_BETWEEN
flexWrapnowrap, wrapNO_WRAP, WRAP
widthType / heightTypefixed, hug, fillFIXED, HUG, FILL
primaryAxisSizingMode / counterAxisSizingModefixed, auto, hug, contentFIXED, AUTO
alignSelfinherit, stretch, start, center, endINHERIT, STRETCH, MIN, CENTER, MAX
positionrelative, absolute, noneAUTO, ABSOLUTE, AUTO
textSizingfixed, height-auto, auto, truncateNONE, HEIGHT, WIDTH_AND_HEIGHT, TRUNCATE
textAlignleft, center, right, justifyLEFT, CENTER, RIGHT, JUSTIFIED
verticalAligntop, center, middle, bottomTOP, CENTER, CENTER, BOTTOM
strokeAligninside, center, outsideINSIDE, CENTER, OUTSIDE
textCasenone, uppercase, lowercase, capitalize, small-capsORIGINAL, UPPER, LOWER, TITLE, SMALL_CAPS
textDecorationnone, underline, line-throughNONE, UNDERLINE, STRIKETHROUGH
boxSizingborder-box, content-boxstrokesIncludedInLayout true/false
canvasStackingfirst-on-top, last-on-topitemReverseZIndex true/false
mask / maskTypealpha, vector, luminance, noneALPHA, VECTOR, LUMINANCE, or isMask: false

Notes:

  • Token references ({...}) are preserved and resolved at apply time.
  • Numeric strings and unit strings (px, rem, em, pt, deg) are normalized to numbers where needed.

Style Property Groups

Layout and container

  • layoutMode, itemSpacing, padding*, layoutWrap, counterAxisSpacing, itemReverseZIndex, strokesIncludedInLayout;
  • grid container fields (gridRowCount, gridColumnCount, gridRowSizes, gridColumnSizes, gridRowGap, gridColumnGap, layoutGrids);
  • auto-layout container sizing (primaryAxisSizingMode, counterAxisSizingMode).

Child layout and positioning

  • layoutSizingHorizontal, layoutSizingVertical, layoutAlign, layoutGrow, layoutPositioning;
  • dimensions and transform (width, height, x, y, rotation);
  • min/max constraints (minWidth, maxWidth, minHeight, maxHeight);
  • constraints via constraintH / constraintV aliases.

Visual

  • fills/background: background, fills, fill, color;
  • stroke/border: border, strokes, strokeWeight, side stroke weights, strokeAlign, dashPattern, strokeCap, strokeJoin;
  • corners: cornerRadius, per-corner radii, cornerSmoothing;
  • effects: effects, boxShadow, backgroundBlur, layerBlur, glass;
  • visibility/clip: display, visible, overflow, mask, isMask, maskType;
  • export/ratio: exportSettings, aspectRatio / targetAspectRatio.

Variable modes

  • explicitVariableModes / variableModes.

Typography

  • composite: typography / font;
  • explicit text fields: fontFamily, fontWeight, fontSize, lineHeight, letterSpacing, textCase, textDecoration, paragraphSpacing, paragraphIndent;
  • text layout: textAlign, verticalAlign, textSizing, textTruncation, maxLines;
  • trim: leadingTrim / verticalTrim.

Instance-control fields

  • component;
  • instanceProperties / properties;
  • nestedInstanceProperties.

none And Reset Semantics

Composition supports CSS-like clearing values.

  • background: "none" or "transparent" => clears fills.
  • color: "none" / fill: "none" => clears text/shape paints.
  • boxShadow: "none", effects: "none", backgroundBlur: "none", layerBlur: "none", glass: "none" => clears effect branches.
  • opacity: "none" => resolves to 1.
  • padding: "none" => all paddings 0.
  • display: "none" => node is generated but made not visible.
  • mask: "none" or mask: false => clears isMask.
  • aspectRatio: "none" / false => unlocks the aspect ratio.

transitions

Transitions are defined at composition root.

Shorthand

JSON
"transitions": {
  "hover-in": "on-hover smart-animate 200ms ease-out"
}

Order:

trigger animation duration easing [direction]

Object form

JSON
"transitions": {
  "hover-in": {
    "trigger": "on-hover",
    "animation": "smart-animate",
    "duration": "200ms",
    "easing": "ease-out",
    "direction": "right",
    "condition": { "variable": "motion.enabled", "op": "==", "value": true }
  }
}

Supported values:

  • trigger: on-hover, on-click, on-press, on-drag, on-enter, on-leave, mouse-up, mouse-down, after-timeout: 500;
  • animation: smart-animate, dissolve, instant, scroll-animate, slide-in, slide-out, push, move-in, move-out;
  • easing: linear, ease-in, ease-out, ease-in-out, ease-in-back, ease-out-back, ease-in-out-back, gentle, quick, bouncy, slow, cubic-bezier(...), spring(...).

Use canonical on-* trigger names in Composition files. If another tool emits short trigger names (hover, click), normalize them before running Generate/Apply.

Do not place transition as a layer style key. Composition transitions are root-level behavior.


themeBinding

Maps a variant axis (usually theme) to variable collection modes.

JSON
"themeBinding": {
  "target": "root",
  "prop": "theme",
  "modes": {
    "light": { "type": "local", "collection": "Themes", "mode": "Light" },
    "dark": {
      "primary": { "type": "local", "collection": "Themes", "mode": "Dark" },
      "fallback": [{ "type": "local", "collection": "Themes", "mode": "Light" }]
    }
  },
  "applyTo": ["variables", "typography", "textStyles", "effectStyles", "paintStyles"]
}

Fields

FieldMeaning
targetBinding target label
propAxis name from props
modesMap of axis value -> mode config
applyToWhat domains to switch

Mode source

ThemeModeSource fields:

  • type: local or library;
  • collection: variable collection name;
  • mode: mode name;
  • library: library name (for library source).

Notes:

  • prop must exist in props.
  • modes.<value> supports two shapes:
    • flat source object (type/collection/mode);
    • wrapped { primary, fallback[] }.

Embedded size / style Blocks

size and style can be embedded in the same file for local component contracts.

JSON
"size": {
  "md": { "height": "40px", "paddingX": "16px" }
},
"style": {
  "primary": { "bg": "{color.brand.primary}" }
}

This is optional and usually used for self-contained component packs.


Advanced: Slot-Host Pipeline Fields

These fields are for advanced host/item systems (segmented controls, tabs, item groups).

slotHostPipeline

JSON
"slotHostPipeline": {
  "hostComponentSetName": "WSegmentControl",
  "hostCompositionTokenName": "WSegmentControl",
  "slotName": "item-group",
  "slotItemComponentSetName": "WSegmentControlItem"
}

slotItemComponentSetName is required.

segmentIconPostPassRefMarkers

JSON
"segmentIconPostPassRefMarkers": ["WSegmentControl", "WSegmentControlItem"]

Alternative location:

JSON
"$figma": {
  "slotHostPipeline": { "slotItemComponentSetName": "WSegmentControlItem" },
  "segmentIconPostPassRefMarkers": ["WSegmentControl"]
}

Generate vs Apply

ModeBest forResult
GenerateNew component/component setCreates new output
ApplyExisting tracked component updatesUpdates existing nodes with tracking

Automatic mode uses context (selected anchor/tracking/local component presence) to choose operation.


Get Code And Grid

Get Code

  • reads selected Figma node(s);
  • generates a Composition JSON draft;
  • useful as bootstrap, then refine manually.

When multi-select is used, output can contain $synthetic: true to indicate synthetic set construction. This is informational.

Grid settings

Grid controls variant layout presentation in generated sets (spacing/grouping/annotations). It does not change token semantics.


Complete Copy-Paste ComponentSet Example

This example includes:

  • props (multi-axis variants);
  • componentProperties (TEXT, BOOLEAN, INSTANCE_SWAP, SLOT);
  • SLOT + INSTANCE in structure;
  • root transitions;
  • themeBinding;
  • local size and style blocks;
  • detailed styles with selector overrides.
JSON
{
  "$type": "composition",
  "name": "WPromoCard",
  "$description": "Promo card with media slot, actions, badge and variant states.",
  "props": {
    "theme": ["light", "dark"],
    "size": ["sm", "md", "lg"],
    "state": ["default", "hover", "active", "disabled"],
    "badge": ["off", "one", "two"],
    "layout": ["media-top", "media-left"]
  },
  "componentProperties": {
    "title": { "type": "TEXT", "layer": "title", "defaultValue": "Promo title" },
    "description": {
      "type": "TEXT",
      "layer": "description",
      "defaultValue": "Short description for this card."
    },
    "showMeta": { "type": "BOOLEAN", "layer": "meta", "defaultValue": true },
    "mediaSlot": {
      "type": "SLOT",
      "layer": "media-slot",
      "defaultValue": "WImageTile",
      "preferred": ["WImageTile", "WIllustration", "WVideoThumb"]
    },
    "leadingIcon": {
      "type": "INSTANCE_SWAP",
      "layer": "leading-icon",
      "defaultValue": "circle-info",
      "preferred": ["circle-info", "star", "flash"]
    },
    "mainAction": {
      "type": "INSTANCE_SWAP",
      "layer": "action-main",
      "defaultValue": "WButton",
      "preferred": ["WButton", "WChipButton"]
    }
  },
  "transitions": {
    "hover-in": {
      "trigger": "on-hover",
      "animation": "smart-animate",
      "duration": "180ms",
      "easing": "ease-out"
    },
    "hover-out": {
      "trigger": "on-leave",
      "animation": "dissolve",
      "duration": "120ms",
      "easing": "ease-in"
    }
  },
  "themeBinding": {
    "target": "card",
    "prop": "theme",
    "modes": {
      "light": { "type": "local", "collection": "Themes", "mode": "Light" },
      "dark": {
        "primary": { "type": "local", "collection": "Themes", "mode": "Dark" },
        "fallback": [{ "type": "local", "collection": "Themes", "mode": "Light" }]
      }
    },
    "applyTo": ["variables", "typography", "textStyles", "effectStyles", "paintStyles"]
  },
  "size": {
    "sm": { "cardWidth": 320, "mediaHeight": 140, "bodyPadding": 14, "actionsGap": 8 },
    "md": { "cardWidth": 360, "mediaHeight": 180, "bodyPadding": 16, "actionsGap": 10 },
    "lg": { "cardWidth": 420, "mediaHeight": 220, "bodyPadding": 20, "actionsGap": 12 }
  },
  "style": {
    "light": {
      "bg": "#FFFFFF",
      "text": "#101828",
      "muted": "#475467",
      "border": "#E7EAF1"
    },
    "dark": {
      "bg": "#121723",
      "text": "#F5F7FB",
      "muted": "#B3BDD1",
      "border": "#2A3142"
    }
  },
  "structure": {
    "tag": "FRAME",
    "class": "card",
    "name": "card",
    "children": [
      {
        "tag": "SLOT",
        "class": "media-slot",
        "name": "mediaSlot",
        "slot": {
          "default": "WImageTile",
          "preferred": ["WImageTile", "WIllustration", "WVideoThumb"]
        },
        "ref": {
          "component": "WImageTile",
          "properties": { "ratio": "16:9" }
        }
      },
      {
        "tag": "FRAME",
        "class": "body",
        "name": "body",
        "children": [
          {
            "tag": "FRAME",
            "class": "header",
            "name": "header",
            "children": [
              {
                "tag": "INSTANCE",
                "class": "leading-icon",
                "name": "leadingIcon",
                "ref": {
                  "component": "circle-info",
                  "properties": { "style": "filled" }
                }
              },
              {
                "tag": "FRAME",
                "class": "title-wrap",
                "name": "titleWrap",
                "children": [
                  { "tag": "TEXT", "class": "title", "name": "title", "content": "Promo title" },
                  { "tag": "TEXT", "class": "meta", "name": "meta", "content": "Updated today" }
                ]
              },
              {
                "tag": "INSTANCE",
                "class": "badge",
                "name": "badge",
                "ref": {
                  "component": "WBadge",
                  "properties": {
                    "size": "md",
                    "variant": "full",
                    "set": "accent",
                    "label": "1"
                  }
                }
              }
            ]
          },
          {
            "tag": "TEXT",
            "class": "description",
            "name": "description",
            "content": "Short description for this card."
          },
          {
            "tag": "FRAME",
            "class": "actions",
            "name": "actions",
            "children": [
              {
                "tag": "INSTANCE",
                "class": "action-main",
                "name": "actionMain",
                "ref": {
                  "component": "WButton",
                  "properties": {
                    "size": "md",
                    "variant": "primary",
                    "label": "Continue"
                  }
                }
              },
              {
                "tag": "INSTANCE",
                "class": "action-secondary",
                "name": "actionSecondary",
                "ref": {
                  "component": "WButton",
                  "properties": {
                    "size": "md",
                    "variant": "ghost",
                    "label": "Later"
                  }
                }
              }
            ]
          }
        ]
      }
    ]
  },
  "styles": {
    ".card": {
      "direction": "column",
      "width": 360,
      "heightType": "hug",
      "gap": 0,
      "borderRadius": 20,
      "border": "1px solid #E7EAF1",
      "background": "#FFFFFF",
      "boxShadow": [
        { "x": 0, "y": 8, "blur": 24, "spread": 0, "color": "rgba(16,24,40,0.14)" }
      ],
      "overflow": "hidden"
    },
    ".media-slot": {
      "widthType": "fill",
      "height": 180,
      "background": [
        {
          "type": "image",
          "url": "https://images.unsplash.com/photo-1498050108023-c5249f4df085",
          "scaleMode": "FILL",
          "opacity": 0.72,
          "blendMode": "NORMAL"
        },
        "rgba(0,0,0,0.20)"
      ]
    },
    ".body": {
      "direction": "column",
      "widthType": "fill",
      "heightType": "hug",
      "gap": 14,
      "padding": "16 16 18 16"
    },
    ".header": {
      "direction": "row",
      "alignItems": "center",
      "justifyContent": "space-between",
      "gap": 10
    },
    ".leading-icon": {
      "width": 20,
      "height": 20,
      "fill": "#344054"
    },
    ".title-wrap": {
      "direction": "column",
      "alignItems": "start",
      "justifyContent": "start",
      "widthType": "fill",
      "heightType": "hug",
      "gap": 2
    },
    ".title": {
      "fontFamily": "Inter",
      "fontWeight": 600,
      "fontSize": 18,
      "lineHeight": "24px",
      "letterSpacing": "0px",
      "textAlign": "left",
      "textSizing": "height-auto",
      "color": "#101828"
    },
    ".meta": {
      "fontFamily": "Inter",
      "fontWeight": 500,
      "fontSize": 12,
      "lineHeight": "16px",
      "textCase": "uppercase",
      "color": "#475467"
    },
    ".badge": {
      "widthType": "hug",
      "heightType": "hug",
      "alignSelf": "end"
    },
    ".description": {
      "fontFamily": "Inter",
      "fontWeight": 400,
      "fontSize": 14,
      "lineHeight": "20px",
      "textSizing": "height-auto",
      "color": "#475467"
    },
    ".actions": {
      "direction": "row",
      "alignItems": "center",
      "justifyContent": "start",
      "widthType": "fill",
      "heightType": "hug",
      "gap": 10
    },
    ".action-main": {
      "widthType": "fill",
      "heightType": "hug",
      "instanceProperties": {
        "variant": "primary",
        "size": "md"
      }
    },
    ".action-secondary": {
      "widthType": "hug",
      "heightType": "hug",
      "instanceProperties": {
        "variant": "ghost",
        "size": "md"
      }
    },

    "$size=sm .card": { "width": 320 },
    "$size=sm .media-slot": { "height": 140 },
    "$size=sm .body": { "padding": "14 14 16 14", "gap": 12 },
    "$size=sm .actions": { "gap": 8 },

    "$size=lg .card": { "width": 420 },
    "$size=lg .media-slot": { "height": 220 },
    "$size=lg .body": { "padding": "20 20 22 20", "gap": 16 },
    "$size=lg .actions": { "gap": 12 },

    "$layout=media-left .card": { "direction": "row" },
    "$layout=media-left .media-slot": { "width": 180, "heightType": "fill" },
    "$layout=media-left .body": { "widthType": "fill" },

    "$state=hover .card": {
      "boxShadow": [
        { "x": 0, "y": 14, "blur": 36, "spread": 0, "color": "rgba(16,24,40,0.20)" }
      ]
    },
    "$state=active .card": {
      "outline": "2px solid #0D6EFD",
      "outlineOffset": "2px"
    },
    "$state=disabled .card": { "opacity": 0.56 },
    "$state=disabled .action-secondary": { "display": "none" },

    "$badge=off .badge": { "display": "none" },
    "$badge=one .badge": { "display": "flex", "instanceProperties": { "label": "1" } },
    "$badge=two .badge": { "display": "flex", "instanceProperties": { "label": "2" } },

    "$theme=dark .card": {
      "background": "#121723",
      "border": "1px solid #2A3142"
    },
    "$theme=dark .leading-icon": { "fill": "#D0D8E8" },
    "$theme=dark .title": { "color": "#F5F7FB" },
    "$theme=dark .meta": { "color": "#B3BDD1" },
    "$theme=dark .description": { "color": "#C4CCE0" }
  }
}

Adaptation checklist

  1. Replace component names (WImageTile, WButton, WBadge, circle-info) with names from your file/library.
  2. If your project has no Themes variable collection, either create one or remove themeBinding.
  3. Start with Generate, then iterate with Apply.
  4. Keep name stable on nodes you target via nested/instance logic.

Agent/Converter Checklist

Use this checklist if you are:

  • generating Composition JSON from Figma (for example via MCP pipeline);
  • writing a JSON -> React/Vue transformer.

A) Figma -> Composition JSON checklist

  1. Set $type: "composition" and a stable name.
  2. Build props only from real variant axes of the selected component set.
  3. For each node, write valid structure.tag (FRAME, TEXT, COMPONENT, INSTANCE, SLOT, RECTANGLE, ELLIPSE, LINE).
  4. Always include class; add stable name for nested-targeted nodes.
  5. For instances, write ref.component and only valid primitive ref.properties.
  6. For slot areas:
    • use reference mode (slot) or children mode (children), never both;
    • if native slot controls are needed, include componentProperties with type: "SLOT".
  7. Put all visual/layout rules into styles (base + selector overrides).
  8. Use root-level transitions and themeBinding, not layer-level hacks.
  9. Validate that referenced components/tokens exist.

B) Composition JSON -> React/Vue transformer checklist

  1. Read structure as the source for element tree.
  2. Use class/path keys for style rule resolution.
  3. Resolve variant selectors ($prop=value .class) against incoming props.
  4. Respect branch visibility semantics (display: "none").
  5. Treat ref as component usage metadata:
    • ref.component -> target component import/name;
    • ref.properties -> props passed to nested component.
  6. Treat componentProperties, themeBinding, slotHostPipeline, and $figma as design-time metadata unless your runtime explicitly supports them.
  7. Preserve token refs ({...}) if your runtime resolves tokens dynamically; otherwise pre-resolve through your token engine.

C) Pre-flight validation before Generate/Apply

  • structure exists and every node has tag + class;
  • styles exists and only contains object values;
  • themeBinding.prop exists in props;
  • no removed legacy keys (adapters, sizeStyles, colorStyles);
  • no SLOT mode conflict (slot with non-empty children).

Limitations And Warnings

  • Unsupported property/node combinations are ignored.
  • Third-party/library instance internals can be partially protected by Figma.
  • NESTED_INSTANCE and ref.nested rely on stable layer names.
  • Large props matrices can produce heavy variant counts.
  • Unknown style keys may be attempted as direct property writes, but this is best-effort behavior, not a guaranteed public contract.