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 (
Applyflow); - 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
- You create/edit JSON manually or bootstrap from Get Code.
- SXL Studio parses and validates the file.
Generatecreates a new component/component set, orApplyupdates tracked nodes.- Tracking is stored in
diff-id.jsonto 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+stylesand 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:
- Open JSON in editor.
- Resolve all validation issues.
- Save.
- Run
GenerateorApply.
Root Fields
| Field | Type | Required | Notes |
|---|---|---|---|
$type | "composition" | ✅ | File type marker |
name | string | ✅ | Component / component set name |
$description | string | — | Figma description on root component/set |
$metadata | unknown JSON | — | Tooling metadata, does not affect rendering |
component | boolean | — | Default true; false generates plain nodes |
props | Record<string, (string | boolean | number)[]> | — | Variant axes |
structure | CompositionStructureNode | ✅ | Layer tree |
styles | Record<string, object> | ✅ | Base styles + selector rules |
componentProperties | Record<string, object> | — | Figma component properties |
transitions | Record<string, TransitionDef> | — | Prototype transitions |
transition | Record<string, TransitionDef> | — | Legacy alias accepted on parse |
themeBinding | ThemeBinding | — | Variant-prop to variable-mode mapping |
size | object | — | Optional embedded size token block |
style | object | — | Optional embedded style token block |
slotHostPipeline | object | — | Advanced host/slot pipeline spec |
segmentIconPostPassRefMarkers | string[] | — | Advanced icon sync markers |
$figma.slotHostPipeline | object | — | Alternative location for slotHostPipeline |
$figma.segmentIconPostPassRefMarkers | string[] | — | Alternative location for icon markers |
selectors | auto-generated | auto | Generated by parser from styles keys; do not author manually |
Important constraints
component: falseis incompatible with non-emptyprops.- Removed legacy keys produce parse errors:
adapters;sizeStyles;colorStyles.
props (Variant Axes)
props defines all allowed values for each axis.
"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.
"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
| Type | layer meaning | defaultValue |
|---|---|---|
TEXT | class in structure | Required by schema, auto-derived from text content if omitted |
BOOLEAN | class in structure | Required by schema, defaults to true if omitted |
INSTANCE_SWAP | class in structure | Required |
SLOT | class in structure | Required |
NESTED_INSTANCE | Nested instance layer name (not class) | Not required |
layer: class vs real layer name
- For
TEXT,BOOLEAN,INSTANCE_SWAP,SLOT: use the structureclass. - For
NESTED_INSTANCE: use the real nested layer name in Figma.
structure
structure is the tree that the generator builds.
"structure": {
"tag": "FRAME",
"class": "root",
"name": "Root",
"children": []
}
Node fields
| Field | Required | Meaning |
|---|---|---|
tag | ✅ | FRAME | TEXT | COMPONENT | INSTANCE | SLOT | RECTANGLE | ELLIPSE | LINE |
class | ✅ | Style/target identifier |
name | — | Explicit Figma layer name |
layer | — | Alias for name when name is omitted |
content | — | Text content (mainly for TEXT) |
description | — | Human note |
ref | — | Component reference object |
slot | — | Native slot config for SLOT reference mode |
children | — | Child nodes |
class and name: practical rules
classis required and is the primary style binding key.namecontrols actual Figma layer naming.- For nested targeting (
ref.nested,NESTED_INSTANCE), stable layer names are critical. - If nested targeting is required, explicitly set
nameand keep it stable over time.
Supported tag values
| Tag | Runtime behavior |
|---|---|
FRAME | Creates frame/container |
TEXT | Creates text layer |
COMPONENT | Creates nested component node |
INSTANCE | Creates instance from ref; falls back to placeholder if unresolved |
SLOT | Creates native slot when available, otherwise fallback behavior |
RECTANGLE | Creates rectangle shape |
ELLIPSE | Creates ellipse shape |
LINE | Creates 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.
{
"tag": "SLOT",
"class": "content-slot",
"slot": { "default": "WButton", "preferred": ["WButton", "WChip"] },
"ref": {
"component": "WButton",
"properties": { "size": "md" }
}
}
2) Children mode
Use children, no slot.
{
"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
SLOTcannot have bothslotand non-emptychildren.SLOT.refis allowed only in reference mode.
ref For INSTANCE / SLOT
ref defines which component to instantiate and how to configure it.
"ref": {
"component": "WNavItem",
"library": "SXL DS",
"key": "abc123",
"properties": { "state": "active" }
}
ref fields
| Field | Meaning |
|---|---|
component | Target component name (required when ref exists) |
library | Library hint |
key | Publish key hint |
properties | Primitive instance property values |
iconBindProperty | Explicit property name for icon instance swap |
icon | Nested icon swap descriptor |
overrides | Child content overrides |
slots | Slot overrides on the referenced instance |
nested | Nested instance updates by layer name |
description | Optional 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.
"nested": {
"Badge": {
"properties": { "label": "3" },
"icon": { "component": "fire-3", "properties": { "style": "filled" } }
}
}
ref.slots
Override slots inside the referenced instance.
"slots": {
"footer": {
"op": "replace",
"nodes": [
{ "component": "WButton", "name": "apply", "properties": { "variant": "primary" } }
]
}
}
Operations:
op | Effect |
|---|---|
replace | Replace slot content |
append | Append nodes |
patch | Selectively patch; supports remove |
styles
styles controls layer appearance/layout and variant-specific overrides.
Authoring formats
All formats below are supported.
Legacy class key
"styles": {
"root": { "direction": "row" }
}
CSS-like class key
"styles": {
".root": { "direction": "row" }
}
Variant selector key
"styles": {
"$state=hover .root": { "background": "{color.brand.hover}" }
}
Nested selector blocks
"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
"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
"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
"styles": {
".card": {
"border": "1px solid #E7EAF1",
"borderRadius": 20
},
"$state=active .card": {
"outline": "2px solid #0D6EFD",
"outlineOffset": "2px"
}
}
4) Shape primitives, mask, and aspect ratio
"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)
"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
"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
"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
"styles": {
".action-main": {
"component": "WButton",
"instanceProperties": {
"variant": "primary",
"size": "md",
"label": "Continue"
}
},
".badge": {
"nestedInstanceProperties": {
"Badge": {
"label": "2"
}
}
}
}
9) Figma-specific metadata
"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
"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 key | Runtime key / behavior |
|---|---|
direction | layoutMode |
justifyContent | primaryAxisAlignItems |
alignItems | counterAxisAlignItems |
alignContent | counterAxisAlignContent |
flexWrap | layoutWrap |
gap | itemSpacing |
wrapGap | counterAxisSpacing |
widthType | layoutSizingHorizontal |
heightType | layoutSizingVertical |
primaryAxisSizingMode | Figma auto-layout container sizing (AUTO/FIXED) |
counterAxisSizingMode | Figma auto-layout container sizing (AUTO/FIXED) |
alignSelf | layoutAlign |
flexGrow | layoutGrow |
position | layoutPositioning |
top / right / bottom / left | Insets (__inset*__), then applied as layout/position constraints |
textSizing | textAutoResize |
textAlign | textAlignHorizontal |
verticalAlign | textAlignVertical |
rotate | rotation |
borderRadius | cornerRadius (or corner split for shorthand) |
cornerSmooth | cornerSmoothing |
rows | gridRowCount |
columns | gridColumnCount |
rowGap | gridRowGap |
columnGap | gridColumnGap |
gridTemplateRows | gridRowSizes |
gridTemplateColumns | gridColumnSizes |
borderWidth | strokeWeight |
borderAlign | strokeAlign |
borderColor | strokes synthesis |
background | fills synthesis (none/transparent clears fills) |
color | text-fill synthesis (__color__) |
fill | shape-fill synthesis (__shapeFills__) |
border | shorthand parser -> strokes/weight/style/align |
outline + outlineOffset | OUTSIDE stroke + optional outline-shadow composite |
boxShadow | effects synthesis (__boxShadow__) |
effects | effects composite (__effectsComposite__) |
backgroundBlur | background blur synthesis |
layerBlur | layer blur synthesis |
glass | glass effect synthesis |
typography / font | typography composite apply |
fontStyle | semantic font-style merge |
content | text content write on TEXT |
component | instance main-component swap (INSTANCE only) |
instanceProperties / properties | instance property assignment |
nestedInstanceProperties | nested instance property assignment by layer name |
constraintH / constraintHorizontal | horizontal constraint alias |
constraintV / constraintVertical | vertical constraint alias |
overflow | clipsContent (hidden/clip => true, visible/auto/scroll => false) |
display | visibility control (none => hidden, non-none => visible) |
aspectRatio / targetAspectRatio | resize to ratio, then lockAspectRatio(); false/none unlocks |
mask | isMask + optional maskType (alpha, vector, luminance) |
isMask / maskType | direct Figma mask fields |
layoutGrids | direct Figma layout grids array |
exportSettings | direct Figma export settings array |
explicitVariableModes / variableModes | explicit variable mode assignment by collection/mode name or id |
Value Mapping (Author Values → Figma Enums)
| Property | Author values | Runtime enum/value |
|---|---|---|
direction | row, column, none, grid | HORIZONTAL, VERTICAL, NONE, GRID |
justifyContent | start, center, end, space-between | MIN, CENTER, MAX, SPACE_BETWEEN |
alignItems | start, center, end, baseline | MIN, CENTER, MAX, BASELINE |
alignContent | auto, space-between | AUTO, SPACE_BETWEEN |
flexWrap | nowrap, wrap | NO_WRAP, WRAP |
widthType / heightType | fixed, hug, fill | FIXED, HUG, FILL |
primaryAxisSizingMode / counterAxisSizingMode | fixed, auto, hug, content | FIXED, AUTO |
alignSelf | inherit, stretch, start, center, end | INHERIT, STRETCH, MIN, CENTER, MAX |
position | relative, absolute, none | AUTO, ABSOLUTE, AUTO |
textSizing | fixed, height-auto, auto, truncate | NONE, HEIGHT, WIDTH_AND_HEIGHT, TRUNCATE |
textAlign | left, center, right, justify | LEFT, CENTER, RIGHT, JUSTIFIED |
verticalAlign | top, center, middle, bottom | TOP, CENTER, CENTER, BOTTOM |
strokeAlign | inside, center, outside | INSIDE, CENTER, OUTSIDE |
textCase | none, uppercase, lowercase, capitalize, small-caps | ORIGINAL, UPPER, LOWER, TITLE, SMALL_CAPS |
textDecoration | none, underline, line-through | NONE, UNDERLINE, STRIKETHROUGH |
boxSizing | border-box, content-box | strokesIncludedInLayout true/false |
canvasStacking | first-on-top, last-on-top | itemReverseZIndex true/false |
mask / maskType | alpha, vector, luminance, none | ALPHA, 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); constraintsviaconstraintH/constraintValiases.
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 to1.padding: "none"=> all paddings0.display: "none"=> node is generated but made not visible.mask: "none"ormask: false=> clearsisMask.aspectRatio: "none"/false=> unlocks the aspect ratio.
transitions
Transitions are defined at composition root.
Shorthand
"transitions": {
"hover-in": "on-hover smart-animate 200ms ease-out"
}
Order:
trigger animation duration easing [direction]
Object form
"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.
"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
| Field | Meaning |
|---|---|
target | Binding target label |
prop | Axis name from props |
modes | Map of axis value -> mode config |
applyTo | What domains to switch |
Mode source
ThemeModeSource fields:
type:localorlibrary;collection: variable collection name;mode: mode name;library: library name (for library source).
Notes:
propmust exist inprops.modes.<value>supports two shapes:- flat source object (
type/collection/mode); - wrapped
{ primary, fallback[] }.
- flat source object (
Embedded size / style Blocks
size and style can be embedded in the same file for local component contracts.
"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
"slotHostPipeline": {
"hostComponentSetName": "WSegmentControl",
"hostCompositionTokenName": "WSegmentControl",
"slotName": "item-group",
"slotItemComponentSetName": "WSegmentControlItem"
}
slotItemComponentSetName is required.
segmentIconPostPassRefMarkers
"segmentIconPostPassRefMarkers": ["WSegmentControl", "WSegmentControlItem"]
Alternative location:
"$figma": {
"slotHostPipeline": { "slotItemComponentSetName": "WSegmentControlItem" },
"segmentIconPostPassRefMarkers": ["WSegmentControl"]
}
Generate vs Apply
| Mode | Best for | Result |
|---|---|---|
Generate | New component/component set | Creates new output |
Apply | Existing tracked component updates | Updates 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+INSTANCEinstructure;- root
transitions; themeBinding;- local
sizeandstyleblocks; - detailed
styleswith selector overrides.
{
"$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
- Replace component names (
WImageTile,WButton,WBadge,circle-info) with names from your file/library. - If your project has no
Themesvariable collection, either create one or removethemeBinding. - Start with
Generate, then iterate withApply. - Keep
namestable 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
- Set
$type: "composition"and a stablename. - Build
propsonly from real variant axes of the selected component set. - For each node, write valid
structure.tag(FRAME,TEXT,COMPONENT,INSTANCE,SLOT,RECTANGLE,ELLIPSE,LINE). - Always include
class; add stablenamefor nested-targeted nodes. - For instances, write
ref.componentand only valid primitiveref.properties. - For slot areas:
- use reference mode (
slot) or children mode (children), never both; - if native slot controls are needed, include
componentPropertieswithtype: "SLOT".
- use reference mode (
- Put all visual/layout rules into
styles(base + selector overrides). - Use root-level
transitionsandthemeBinding, not layer-level hacks. - Validate that referenced components/tokens exist.
B) Composition JSON -> React/Vue transformer checklist
- Read
structureas the source for element tree. - Use
class/path keys for style rule resolution. - Resolve variant selectors (
$prop=value .class) against incoming props. - Respect branch visibility semantics (
display: "none"). - Treat
refas component usage metadata:ref.component-> target component import/name;ref.properties-> props passed to nested component.
- Treat
componentProperties,themeBinding,slotHostPipeline, and$figmaas design-time metadata unless your runtime explicitly supports them. - Preserve token refs (
{...}) if your runtime resolves tokens dynamically; otherwise pre-resolve through your token engine.
C) Pre-flight validation before Generate/Apply
structureexists and every node hastag+class;stylesexists and only contains object values;themeBinding.propexists inprops;- no removed legacy keys (
adapters,sizeStyles,colorStyles); - no SLOT mode conflict (
slotwith non-emptychildren).
Limitations And Warnings
- Unsupported property/node combinations are ignored.
- Third-party/library instance internals can be partially protected by Figma.
NESTED_INSTANCEandref.nestedrely on stable layer names.- Large
propsmatrices 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.