Composition
Полный справочник по типу Composition в SXL Studio: корневые поля, структура, теги, props, component properties, styles, transitions и theme binding — каждая секция начинается с таблицы полей.
Overview
$type: "composition" — это контракт SXL Studio, который описывает Figma-компонент в виде JSON. Это единый источник правды, превращающий замысел дизайна в повторяемые запуски Generate и Apply в Figma.
Вкратце, composition собирается из этих блоков:
| Блок | Ключ(и) | Назначение |
|---|---|---|
| Структура | structure | Дерево слоёв |
| Варианты | props / states | Оси вариантов и взаимоисключающие состояния |
| Стилизация | styles | Layout, оформление и переопределения по вариантам |
| Свойства | componentProperties | Нативные свойства Figma на инстансах |
| Поведение | transitions / themeBinding | Прототипирование и светлая/тёмная тема |
Ключевая идея Один JSON-файл описывает весь набор компонентов — его форму, варианты и стилизацию — поэтому один и тот же результат можно сгенерировать, обновить и передать инженерам или агентам без ручной пересборки.
Растровые изображения и отражённые слои
Доступно с версии 2.9.4.
Заливка изображением может использовать src: "data:image/png;base64,..." (вместо многоточия нужны настоящие байты), HTTP(S)-источник или imageHash изображения, уже доступного в том же файле Figma. Явные поля источника имеют приоритет: src, затем url, href, затем imageHash. Ошибочный явный источник вызывает ошибку, без подстановки по хешу. Один хеш не обеспечивает переносимость между файлами.
Встроенные источники поддерживают PNG, JPEG и GIF, не более 4 МиБ декодированных байтов на изображение. Экспорт полного JSON ограничивает суммарный текст встроенных data URL величиной 16 МиБ символов, включая повторные вхождения. Figma документирует максимальный размер изображения 4096 пикселей по каждой оси. Эти ограничения не гарантируют успешную загрузку любого удалённого URL.
Get Code по умолчанию встраивает исходные байты изображений в полный JSON. Если байты недоступны или превышают поддерживаемые ограничения, экспорт завершается явной ошибкой. Исходные байты не включают обрезку, фильтры и эффекты слоя: они остаются отдельными свойствами. Заливки сохраняют imageTransform для CROP, фильтры, видимость и режим наложения. Обычные вложенные прямоугольники остаются слоями RECTANGLE.
styles.relativeTransform принимает конечную нативную матрицу 2×3 с независимыми осями единичной длины, в том числе отражение. Размер задаётся через width и height; это не CSS-масштабирование. Позиция корня на странице сохраняется, а положение дочерних слоёв в потоке определяет auto-layout. Для абсолютно позиционированных слоёв и свободного размещения матрица также задаёт позицию дочернего слоя. Эта трансформация ноды отделена от трансформации обрезки изображения.
Смешанные списки эффектов сохраняют порядок, включая GLASS без нативных привязок переменных. Режим Raw — явно выбранный снимок буквальных значений; связи с переменными он не сохраняет. Figma помечает GLASS как beta, поэтому поддержка в runtime и визуальная приёмка ещё требуют проверки.
Масштаб инстанса
В разрабатываемой версии 2.9.5 поле scale задаёт абсолютный масштаб явно объявленного слоя INSTANCE или ICON, включая INSTANCE внутри нативного SLOT в основном компоненте.
{
"structure": {
"tag": "FRAME",
"class": "root",
"children": [
{
"tag": "INSTANCE",
"class": "preview",
"ref": { "component": "PreviewCard" }
}
]
},
"styles": {
".root": { "direction": "column" },
".preview": { "width": 200, "height": 80, "scale": 1.03 }
}
}
width и height задаются до масштабирования: пример даёт размер 206 × 82,4. Повторный Apply с 1.03 не накапливает масштаб. Изменение scale задаёт новый абсолютный коэффициент; удаление последнего авторского scale возвращает инстанс к 1. Ручной масштаб слоя, которому JSON никогда не задавал scale, сохраняется.
Допустимо число от 0.01 до 100. Строки и ссылки на токены не поддерживаются. Масштабирование привязано к верхнему левому углу; auto-layout учитывает получившийся размер и управляет положением слоя. Сочетание с размером FILL отклоняется.
Числовые min/max ограничения масштабируются вместе с инстансом, сохраняя ссылки на переменные: maxWidth 210 при scale: 1.03 даёт предел 216,3. Значение исходной переменной не меняется.
При заблокированной пропорции Figma связывает оси: запись переменной размера или min/max на одной оси может снять связи другой. Правило точное: под замком каждая пара width/height, minWidth/minHeight, maxWidth/maxHeight хранит одну переменную; разные пары друг другу не мешают (minWidth вместе с maxHeight — допустимо), а один и тот же токен на обеих сторонах пары допустим: Figma оставляет одну связь, размер следует за замком, результат равен авторскому (идиома иконок width: {size}, height: {size}). Ошибка авторства — разные переменные на двух сторонах одной пары при замке в JSON: Generate и Apply останавливаются до изменений со статусом «ошибка» (aspect-both-axes), называют слой, пару и токены и предлагают исправление в JSON — оставить одну переменную в паре (сторону, которую ведёт родитель; вторая следует за пропорцией) или снять замок с этого слоя. Замок, который стоит на канвасе, а не в JSON (унаследованный от главного компонента или поставленный вручную), ошибкой не считается: переменная авторской стороны записывается, связь второй стороны пары освобождается, результат получает предупреждение aspect-lock-settled. Сбой любого стиля корня варианта попадает в ошибки результата, но не оставляет вариант пустым. Унаследованный замок экземпляра (например, иконки, чей главный компонент заблокирован) конфликтом не считается: JSON не управляет чужим замком, экземпляр получает переменные обычным путём, Figma оставляет одну переменную в каждой паре, а размер следует за замком. При одном и том же токене на обеих осях результат полностью эквивалентен авторскому, и Smart Apply не сообщает о расхождении; при разных токенах Smart сохраняет живую сторону и предупреждает. Замок, оставшийся на управляемом слое без ключа в JSON, при полном Apply снимается до записи переменных. Это поведение Figma, а не плагина: под замком каждая пара width/height, minWidth/minHeight, maxWidth/maxHeight хранит только одну переменную — вторая снимается, а снятие замка её не возвращает. Для карточек в сетке задайте ведущую ось: widthType: "fill" или переменная ширины, minWidth/maxWidth и aspectRatio — высота и её пределы следуют за пропорцией, переменные высоты не нужны. Используйте ограничения ведущей оси вместе с пропорцией либо снимите блокировку для независимых ограничений обеих осей. Поддерживаемые настройки JSON — aspectRatio / targetAspectRatio и constrainProportions; lockAspectRatio — имя метода Figma, которое не принимается как настройка JSON.
scale нельзя задавать корню композиции, контейнерам FRAME / SLOT, через ref.styles или инстансу внутри другого инстанса. Для default-содержимого слота объявите дочерний INSTANCE и задайте масштаб его классу в styles. Авторские типы слоёв, размещение scale и сочетание с FILL в styles проверяются до генерации. Ограничения, зависящие от текущего инстанса Figma, проверяются при применении. Произвольное масштабирование контейнера не поддерживается: нативная операция Figma может снимать числовые привязки его дочерних слоёв.
Hover карточки без смещения соседей
Чтобы увеличить всю карточку — изображение, бейджи и кнопку — внутри Auto Layout или Grid, оставьте её ячейку в потоке, а увеличиваемый экземпляр разместите внутри абсолютно. Ячейка сохраняет размер в обоих состояниях, поэтому соседние карточки не сдвигаются. Отдельное свойство scaleMode не требуется.
Подготовьте компонент CardArtwork со всем содержимым карточки. В примере его пропорция — 172:230. Создайте числовые переменные card.width, card.height, card.minWidth, card.maxWidth, card.minHeight, card.maxHeight; например, со значениями 172, 230, 86, 258, 115 и 345.
Сначала сгенерируйте CardEnvelope. Его корень хранит размеры и все четыре ограничения без блокировки пропорций. Пропорция задана явно у внутреннего экземпляра CardArtwork:
{
"$type": "composition",
"name": "CardEnvelope",
"structure": {
"tag": "FRAME",
"class": "envelope",
"children": [
{
"tag": "INSTANCE",
"class": "artwork",
"ref": {
"component": "CardArtwork"
}
}
]
},
"styles": {
".envelope": {
"direction": "row",
"widthType": "fixed",
"heightType": "fixed",
"width": "{card.width}",
"height": "{card.height}",
"minWidth": "{card.minWidth}",
"maxWidth": "{card.maxWidth}",
"minHeight": "{card.minHeight}",
"maxHeight": "{card.maxHeight}",
"justifyContent": "center",
"alignItems": "center",
"clipsContent": false,
"constrainProportions": false
},
".artwork": {
"widthType": "fill",
"heightType": "fixed",
"constrainProportions": true,
"aspectRatio": "172/230"
}
}
}
Затем сгенерируйте CardCell. Размер ячейки совпадает с базовым размером оболочки, а абсолютный экземпляр оболочки увеличивается при hover:
{
"$type": "composition",
"name": "CardCell",
"props": {
"state": ["default", "hover"]
},
"structure": {
"tag": "FRAME",
"class": "cell",
"children": [
{
"tag": "INSTANCE",
"class": "card",
"ref": {
"component": "CardEnvelope"
}
}
]
},
"styles": {
".cell": {
"direction": "row",
"widthType": "fixed",
"heightType": "fixed",
"width": "{card.width}",
"height": "{card.height}",
"minWidth": "{card.minWidth}",
"maxWidth": "{card.maxWidth}",
"minHeight": "{card.minHeight}",
"maxHeight": "{card.maxHeight}",
"clipsContent": false,
"constrainProportions": false
},
".card": {
"position": "absolute",
"widthType": "fixed",
"heightType": "fixed",
"width": "{card.width}",
"height": "{card.height}",
"scale": 1,
"x": 0,
"y": 0,
"constraintH": "scale",
"constraintV": "scale",
"constrainProportions": false
},
"$state=hover .card": {
"scale": 1.03,
"x": "-1.5%",
"y": "-1.5%"
}
},
"transitions": {
"$state=default -> $state=hover": "on-hover smart-animate 160ms ease-out"
}
}
Разместите несколько экземпляров CardCell со state: "default" обычными детьми строки Auto Layout или Grid. Переход принадлежит каждой карточке; реакция на всей строке не нужна. При scale: 1.03 карточка 172 × 230 становится 177,16 × 236,9, а ячейка остаётся 172 × 230.
Смещение -1.5% на каждой оси компенсирует половину увеличения; явные constraintH / constraintV: "scale" сохраняют пропорциональное смещение при изменении размера ячейки. clipsContent: false позволяет карточке выходить за ячейку; проверьте обрезку и у внешних контейнеров.
Здесь переменные обеих осей и блокировка пропорций находятся на разных слоях. Это не обходит ограничения Figma на одном узле. Внутренний компонент подстраивается по ширине: если независимые ограничения зададут ячейке другую пропорцию, содержимое может выйти за её высоту. Рецепт не предполагает универсального вписывания по двум осям.
Updates
История релизов типа Composition. Новый релиз — сверху; раскройте запись, чтобы увидеть изменения.
2.9.2Актуальный
- В
effectsстилей композиции принимаются все слои эффектов Figma: прогрессивное размытие, шум, текстура, стекло и шейдеры, с теми же ключами, что у токенов типаeffects. - Повторный Apply обычного компонента больше не запускает сценарий разворачивания набора вариантов (2.9.0).
2.8.10
- Корневые
$descriptionMarkdownиdocumentationLinksподдерживают round-trip через Generate, Apply и Get Code. - Ссылку можно записать как чистый HTTP(S) URL или одну Markdown-ссылку на всю строку; в Figma SXL Studio передаёт чистый URL назначения.
- Get Code теперь требует уникальную переносимую source identity и сохраняет авторские описания structure и ref.
2.7.7Предыдущий
ref.stylesприменяет нормализованные стили к корневому referenced-инстансу вINSTANCErefs и default-инстансахSLOTв reference mode.refStylesв style-блоке задаёт вариантные переопределения для этого referenced root, не стилизуя абстрактный host слота и preferred swaps.
2.7.1Предыдущий
- Компактные оси
statesдля взаимоисключающих состояний вродеloadingиdisabled— без раздувания декартова произведения. - Единая модель раскладки Composition Grid — общая для превью и сгенерированных наборов.
- Фигуры
VECTORдля кастомных силуэтов, включаяvectorCornerRadiiна токенах. - Вариантные переопределения
refдля wrapper-наборов с одним вложеннымINSTANCE. - Более понятное поведение
displayиvisibleдля условных веток. - Быстрее
Generate/Applyв больших файлах, особенно для наборов с множеством слотов и инстансов.
Где используется
Используйте Composition, когда нужна предсказуемая и повторяемая сборка компонентов в дизайн-системе.
Частые сценарии:
- генерация новых компонентов / наборов из token-файла;
- обновление существующих компонентов без пересборки (
Apply); - хранение структуры вариантов, содержимого слотов и правил стилизации в одном источнике;
- передача детерминированной модели компонента инженерам и агентам.
Как это работает в SXL Studio
- Вы пишете JSON вручную или бутстрапите его через Get Code.
- SXL Studio парсит и валидирует файл.
Generateсоздаёт новый компонент/набор,Applyобновляет отслеживаемые ноды.- Трекинг хранится в
diff-id.json, чтобы обновления были стабильны между запусками.
Трансформация в React / Vue 3
Composition JSON машиночитаем и достаточно стабилен для собственных скриптов-трансформеров. Плагин использует его для генерации в Figma, а вы можете читать structure + styles и мапить компонент в свой фреймворк. В Dev Mode SXL Studio также выдаёт готовый codegen (в том числе Vue 3), который можно взять за эталон.
Generate
Generate собирает новый компонент или набор из JSON: читает structure, разворачивает props в варианты, применяет styles и резолвит ссылки на токены ({path.to.token}) на этапе сборки. Используйте для компонента, которого ещё нет в Figma.
Предпроверка перед Generate / Apply
Сначала исправьте ошибки в JSON-редакторе. Типичные причины:
- нерезолвнутые ссылки на токены (
{path.to.token}); - невалидный синтаксис алиасов;
- нарушения схемы (например, некорректная форма
SLOT).
Рекомендуемый порядок: открыть JSON → устранить все ошибки валидации → сохранить → запустить Generate или Apply.
Get Code
Get Code читает выбранные ноды в Figma и генерирует черновик Composition JSON — быструю стартовую точку, которую вы затем дорабатываете вручную. Семантически пустой шаблон редактора можно заполнить из поддерживаемого одиночного или множественного выделения; при multi-select в выводе может появиться $synthetic: true — информационный признак синтетической сборки набора.
Обновление непустой Composition намеренно работает строже. Её переносимая identity из $extensions["sxl.studio"].composition должна однозначно разрешаться в выбранный Component или ComponentSet; выбранный Instance разрешается через корень его main component. Одного совпадения имени недостаточно. Неоднозначная или нечитаемая identity, корень другой Composition, множественное выделение либо обычный Frame останавливают Get Code без изменения редактора. Результат также отбрасывается, если содержимое редактора изменилось во время экспорта из Figma.
Get Code сохраняет авторские расширения на структурно доказанных нодах, включая structure.description и ref.description. Сгенерированные поля структуры и стилей остаются приоритетными, а сгенерированный снимок и итоговый объединённый документ обязаны пройти Composition parser до замены содержимого редактора. Instance из ComponentSet экспортируется как снимок с component: false без несовместимых вариантных props и states.
Grid settings
Grid управляет тем, как варианты раскладываются на канвасе в сгенерированном наборе. Меняется только размещение — не семантика токенов и не контракт JSON.
- Columns / Rows явно назначают оси вариантов; несколько осей в одной зоне вкладываются снаружи внутрь.
- оси
statesне размещаются в Columns / Rows — они выводятся отдельной state-полосой снизу или справа. - отступы разделены по смыслу: variant gap, prop/group gap, padding набора, label gap и state-band gap.
- аннотации сетки рендерятся вне набора на тех же дорожках размещения, поэтому подписи совпадают с превью и выводом. При переносе набора Apply перемещает его подписи и убирает прежние на другой странице. Повторный Apply сохраняет неизменные подписи и исправляет ручные изменения текста или оформления.
Рекомендация В больших файлах Figma тот же JSON остаётся стабильным, а SXL Studio уменьшает лишнюю перестилизацию для слот-тяжёлых компонентов. Держите значения class / name стабильными на слоях, которые адресуются из styles, и используйте states для взаимоисключающих состояний вместо разворачивания всех булевых комбинаций.
Apply
Apply обновляет существующий отслеживаемый компонент на месте, используя трекинг из diff-id.json, поэтому идентичность и инстансы сохраняются.
Действия доступны в контекстном меню бейджа композиции в редакторе токенов и у папки с композициями:
| Действие | Для чего |
|---|---|
| Generate | Создаёт новый компонент или набор вариантов из JSON |
| Smart Apply | Обновляет отслеживаемый компонент на месте, а если его ещё нет в файле, создаёт новый |
| Repair Apply | Пересобирает варианты отслеживаемого компонента, восстанавливая структуру после ручных правок |
| Adopt composition | Связывает JSON с уже существующим компонентом, выбранным на холсте |
| Audit composition | Сравнивает компонент на холсте с JSON и показывает расхождения без изменений |
Переопределения экземпляров при Apply
Экземпляры обновляемого главного компонента сохраняют свои переопределения (заменённая иконка, изменённый текст, булевы свойства, привязки) — обновление идёт на месте по идентификаторам слоёв. Apply это проверяет, а не предполагает: до изменения главных компонентов фиксируются реальные переопределения их экземпляров в документе (по нативным данным о переопределениях и отличию от объявленных значений по умолчанию; для вложенных слоёв — по пути слоя), после обновления и снова после завершающих проходов они читаются обратно. Сброшенное совместимое переопределение записывается обратно и проверяется; унаследованные значения по умолчанию не считаются переопределениями и следуют новому JSON; свойство или слой, удалённые по JSON, — ожидаемая потеря, она перечисляется в предупреждениях. Если совместимое переопределение восстановить нельзя, результат становится неуспешным без отметок об успехе, а сообщение называет экземпляр и свойство. Проверка ограничена первыми 400 экземплярами; сверх этого предупреждение сообщает, что остальные не проверены.
Статусы результата и восстановление
Результат Generate, Smart Apply и Repair Apply показывается одним из трёх статусов. Успех (зелёная галочка): изменения применены, замечаний нет; информационные сводки (например, проверка переопределений экземпляров, которая ничего не восстанавливала и не теряла) статус не меняют. Предупреждение (жёлтый треугольник): изменения применены, но есть замечания — каждое перечислено отдельной строкой с рекомендацией, что исправить; там, где это уместно, уведомление сразу предлагает кнопку Repair. Ошибка (красный крест в круге): операция не завершена; пункты перечисляют, что именно не так и как исправить. Для ошибки уведомление предлагает Auto-fix: он заново связывает существующие варианты с композицией, дожидается готовности Local Workspace, выполняет Repair Apply и затем Smart Apply с якорем на корень, отслеживаемый под именем композиции (это же снимает неоднозначность, когда два файла несут одну скопированную идентичность), а результат снова показывается с теми же тремя статусами. Auto-fix не меняет JSON, поэтому кнопки нет только у чистых конфликтов авторинга — замок пропорций с переменными обеих осей, неразрешённая ссылка, миграция без карты, невалидный файл композиции: они остаются ошибкой с рекомендацией, что исправить в JSON.
Замечания и ошибки показываются списком. У каждого пункта значок серьёзности: красный крест — ошибка, жёлтый треугольник — замечание, серый «i» — справочная строка; сначала идут ошибки, затем замечания. Длинное сообщение разбирается на заголовок и отдельные строки «что не так» (например, каждая конфликтующая пара width/height — своей строкой), а шаги исправления перечислены отдельно, каждый со стрелкой «→»; имена слоёв, свойства и токены {…} выделены. Если один и тот же набор шагов относится к нескольким пунктам, он показан один раз над списком. Рядом с заголовком стоит счёт «N errors · M notices», список длиннее восьми пунктов сворачивается до восьми с кнопкой Show N more, а Copy all копирует пункты в том же виде — заголовок, строки и шаги. Тот же список используют уведомления экспорта переменных и стилей, Apply Tokens и Apply Data.
Те же данные возвращаются Bridge-командами generate_composition, apply_composition и auto_fix_composition в полях status, diagnostics (severity, code, message, details — факты по строкам, fixes — шаги исправления по порядку, recommendation — то же одной строкой, action) и suggestedAction.
Сохранение файла токенов средствами плагина на несколько секунд переводит Local Workspace в перезагрузку. Generate и Apply ждут её окончания перед записью, а запись трекинга после изменения канваса повторяется, когда рабочая область снова готова, — операция не остаётся незавершённой из-за этого окна. Если рабочая область требует перезагрузки (результат записи через Bridge не подтверждён), Generate и Apply останавливаются до изменений с ошибкой о готовности; перезагрузку выполняет кнопка Reload в баннере или команда reload_local_workspace для агентов — та же операция чтения с диска, черновики редактора не затрагиваются. Изменение файла на диске, попавшее в окно работающей операции, применяется в её точке ожидания готовности, а не после таймаута. Конфликт несохранённого черновика редактора с диском ожиданием не снимается: операция останавливается с указанием причины, а reload_local_workspace отвечает отказом с той же причиной — решение принимает пользователь в панели Tokens.
Смена вида слоя: FRAME ↔ SLOT
Если в structure слой FRAME получает тег SLOT, Apply на самостоятельном компоненте заменяет его нативным слотом на том же месте (вложенные слои переезжают внутрь и сохраняют идентификаторы; идентификатор самого контейнера меняется — слот создаётся только через createSlot(), который заводит и свойство слота). Внутри набора вариантов слой остаётся контейнером на своём месте (идентификатор сохраняется): набор получает новое свойство слота, и проход привязки слотов связывает контейнер с ним. Обратная смена SLOT → FRAME пересобирает слой как обычный фрейм и удаляет слот. Уже связанные заглушки и слои, для которых свойство слота с тем же именем существует, не пересоздаются.
Свойство INSTANCE_SWAP и вариантные ссылки
Значение по умолчанию свойства INSTANCE_SWAP берётся из componentProperties.<имя>.defaultValue в JSON, когда оно разрешается в компонент; текущий компонент на слое используется только как запасной вариант. Слой, привязанный к такому свойству, показывает значение по умолчанию во всех вариантах: вариантная замена компонента для этого слоя ("$size=md .root": { ".swap": { "component": "…" } }) в Figma непредставима и сообщается предупреждением. Для слоя без привязки к INSTANCE_SWAP вариантная замена компонента применяется при Generate, Smart и Repair, и аудит ожидает её же.
Неразрешённые ссылки
Если ссылка слоя INSTANCE / ICON не разрешается (нет доступа к библиотеке, неверный ключ, не экспортированная переменная в свойстве), результат Generate и Apply — ошибка с первичной причиной, а не успешное обновление: отметки об успехе не записываются, слой остаётся пустой помеченной заглушкой, операции только для инстансов (например scale) к ней не применяются. Существующий узел на этом месте при неразрешённой ссылке сохраняется вместе с идентификатором и переопределениями: повторный неуспешный Apply не создаёт новую заглушку. После исправления ссылки повторный Apply заменяет слой на инстанс, сохраняя набор, главные компоненты и соседние слои. JSON — источник истины для управляемого главного компонента: слой другого типа на месте авторского инстанса приводится к JSON вместе со своим содержимым; переопределения экземпляров — отдельный уровень и не сбрасываются этим.
Восстановление привязок в Smart Apply
В разрабатываемой версии 2.9.5 Smart Apply проверяет прямые привязки управляемых слоёв и при неизменном JSON, и после изменения другого стилевого блока. Поддерживаемые числовые поля, видимость, переменные цвета в однотонных заливках и обводках, а также однородные текстовые поля могут восстановить снятую или неверную связь с переменной. Литеральные значения ref.properties собственных вложенных экземпляров главного компонента (текст, булевы, варианты) тоже сверяются с живыми: если значение разошлось — например, свойство переименовали в компоненте-источнике или изменили вручную внутри главного компонента, — Smart записывает значение из JSON обратно.
Для default-инстанса нативного слота восстанавливаются только явно заданные авторские стили при подтверждённых прежнем компоненте и свойствах default-содержимого. Ручная замена содержимого или изменение его component properties сохраняются. Внутренние слои инстанса, которые наследуют оформление от другого main-компонента, не становятся управляемыми слоями родительской композиции.
Эта проверка привязок не сбрасывает связи с PaintStyle / TextStyle, смешанное форматирование текстовых диапазонов и выбранные режимы переменных. Она не заменяет полноценный Repair структуры. Обычное применение явного изменения JSON по-прежнему следует контракту соответствующего поля: например, авторский fontSize может переопределить TextStyle.
Разрешение цели и сохранение идентичности
Каждый выбранный корень composition обрабатывается со своим явным anchor. SXL Studio разрешает всё выделение до первой мутации canvas, поэтому цепочка sm → md и md → lg не позволяет первому переименованию забрать цель второго JSON-файла.
- Уникальная живая identity обновляет этот Component или ComponentSet на месте. ID ноды Figma остаётся стабильным, поэтому существующие инстансы сохраняют связь с компонентом.
- Smart Apply при устаревшей или чужой связи без живого кандидата в текущем файле создаёт новый компонент.
- Явный Apply без подходящей цели просит выбрать нужный корень или использовать Generate. Неоднозначные, конфликтующие или не полностью просканированные кандидаты останавливаются до мутации.
- Apply сохраняет overrides пользовательских инстансов. Он не выполняет blanket reset текста, component properties, instance swaps и свойств, которых нет в composition.
- Generate и Apply из UI плагина не требуют Bridge. Remote Connect и Git Sync Local Storage по-прежнему используют Bridge для собственных transport/storage-контрактов.
SXL Studio управляет переносимой source identity в $extensions["sxl.studio"].composition. Старые JSON без этого блока остаются поддержанными; успешный Generate или anchored Apply пытается добавить его, не переписывая посторонние $extensions:
{
"$extensions": {
"sxl.studio": {
"composition": {
"id": "11111111-1111-4111-8111-111111111111",
"roots": [
{
"nodeId": "21649:1480",
"key": "figma-component-key",
"type": "COMPONENT_SET"
}
]
}
}
}
}
Это управляемые метаданные, а не авторский input. Если storage временно недоступен, canvas-операция остаётся успешной и сообщает warning; persistence identity повторяется в одном из следующих запусков.
Adopt и Audit для существующих компонентов
Компонент, созданный вручную до появления Composition JSON, удалять не нужно. Используйте контекстное меню бейджа composition:
| Действие | Что делает | Когда использовать |
|---|---|---|
Adopt composition | Связывает выбранный компонент/набор с JSON без пересборки слоёв. | Когда компонент в Figma уже есть и будущий Apply должен обновлять его. |
Audit composition | Сравнивает выбранный/связанный компонент с JSON и сообщает статус синхронизации. | Перед adopt для легаси-компонента, после ручных правок или перед repair. |
Adopt консервативен: сохраняет идентичность компонента (инстансы в других файлах не теряют связь), проверяет соответствие имён вариантов матрице из JSON и не переписывает слои, стили и свойства, пока audit/apply не подтвердит реальную структуру.
Root fields
Корневые ключи файла composition.
| Поле | Тип | Обязательно | Примечания |
|---|---|---|---|
$type | "composition" | ✅ | Маркер типа файла |
name | string | ✅ | Имя компонента / набора |
structure | нода | ✅ | Дерево слоёв |
styles | объект | ✅ | Базовые стили + правила селекторов |
props | объект | — | Оси вариантов |
states | string[] | — | Взаимоисключающие оси, взятые из props |
componentProperties | объект | — | Нативные свойства Figma |
transitions | объект | — | Прототип-переходы (transition в ед. ч. принимается как легаси-алиас) |
themeBinding | объект | — | Ось варианта → режимы коллекции переменных |
component | boolean | — | По умолчанию true; false строит plain-ноды и запрещает непустые props |
$description | string | — | Описание Figma на корневом компоненте/наборе |
$descriptionMarkdown | string | — | Форматированное описание Figma на корневом компоненте/наборе |
documentationLinks | { uri: string }[] | — | Ноль или одна корневая ссылка; чистый URL или одна Markdown-ссылка |
$metadata | any | — | Непрозрачные метаданные для тулинга; на генерацию не влияют |
size / style | объект | — | Опциональные встроенные token-блоки (см. Advanced-поля) |
selectors | auto | auto | Генерируется парсером из ключей styles — вручную не писать |
Внимание Удалённые легаси-ключи дают ошибку парсинга: adapters, sizeStyles, colorStyles. А component: false несовместим с непустыми props.
Корневые описания и ссылка на документацию
$descriptionMarkdown хранит форматированное описание компонента и может содержать обычный Markdown. documentationLinks — отдельное поле Link в Figma, которое поддерживает не более одного элемента. В uri можно указать чистый абсолютный HTTP(S) URL или одну Markdown-ссылку, занимающую всю строку:
{
"$descriptionMarkdown": "Читайте [документацию Popover](https://docs.sxl.team/ds/components/popover).",
"documentationLinks": [
{
"uri": "[Документация Popover](https://docs.sxl.team/ds/components/popover)"
}
]
}
Перед записью в Figma SXL Studio нормализует второй вариант до https://docs.sxl.team/ds/components/popover. Относительные URL, небезопасные схемы, текст вокруг ссылки, несколько Markdown-ссылок и Markdown title отклоняются. Authority допускает только ASCII; для интернациональных доменов используйте punycode. Если поле аннотации отсутствует, Apply или повторный Generate сохраняет ручное значение; "" очищает описание, а [] очищает Link.
Structure
structure — это дерево слоёв, которое строит генератор. У каждой ноды обязательны tag и class; всё остальное опционально.
Поля ноды
| Поле | Обязательно | Что делает | Аналог в Figma | Аналог во Frontend |
|---|---|---|---|---|
tag | ✅ | Тип создаваемой ноды | Тип слоя | Тип элемента |
class | ✅ | Ключ привязки стилей/цели | Селектор слоя | className |
name | — | Явное имя слоя в Figma | Имя слоя | data-name / подпись |
layer | — | Алиас name, если name не задан | Имя слоя | data-name |
content | — | Текстовое содержимое (в основном TEXT) | Значение текста | текст-нода / children |
description | — | Заметка для человека, без визуала | Описание слоя | комментарий в коде |
ref | — | Какой компонент инстанцировать + конфиг | Instance → main comp. | импортированный компонент |
slot | — | Конфиг нативного слота (reference mode) | Свойство Slot | <slot> / children |
children | — | Вложенные дочерние ноды | Вложенные слои | дочерние элементы |
vectorPaths | — | SVG-подобные path-данные (только VECTOR) | Vector paths | <path d="…"> |
vectorNetwork | — | Редактируемая геометрия вершин (VECTOR) | Vector network | — |
viewBox | — | Координатный бокс [x, y, w, h] (VECTOR) | — | SVG viewBox |
"structure": {
"tag": "FRAME",
"class": "root",
"name": "Root",
"children": []
}
JSON-пример на каждое поле:
{ "tag": "FRAME", "class": "card" }
{ "tag": "TEXT", "class": "label", "name": "Label", "content": "Continue" }
{
"tag": "INSTANCE",
"class": "icon",
"ref": { "component": "circle-info", "properties": { "size": "md" } }
}
{
"tag": "SLOT",
"class": "content-slot",
"slot": { "default": "WButton", "preferred": ["WButton", "WChip"] }
}
{
"tag": "FRAME",
"class": "row",
"description": "Строка шапки",
"children": [{ "tag": "TEXT", "class": "title", "content": "Title" }]
}
Примечание class — ключ стилей, name — имя слоя в Figma. Для вложенной адресации (ref.nested, NESTED_INSTANCE) всегда задавайте явный стабильный name.
Поддерживаемые значения tag
| Tag | Что создаёт | Аналог в Figma | Аналог во Frontend | Ограничения |
|---|---|---|---|---|
FRAME | Контейнер / auto-layout фрейм | Frame / Auto Layout | <div> (flex-контейнер) | — |
TEXT | Текстовый слой | Text | <span> / <p> | значение из content |
COMPONENT | Вложенная нода main-компонента | Component | определение компонента | — |
INSTANCE | Инстанс из ref | Instance | использование <Component /> | нужен ref; плейсхолдер, если не резолвится |
ICON | Иконка-инстанс, резолвится из ref | Icon instance | <Icon /> | как INSTANCE; color перекрашивает глиф |
SLOT | Нативный слот (или fallback) | Slot | <slot> / {children} | reference или children mode, не оба |
RECTANGLE | Прямоугольник | Rectangle | блочный <div> | заливка через fill |
ELLIPSE | Эллипс | Ellipse | border-radius: 50% div | — |
LINE | Линия | Line | <hr> / разделитель | — |
VECTOR | Кастомный вектор из path / vector network | Vector (Pen) | инлайновый <svg><path> | не импорт «сырого» SVG |
JSON-пример на каждый тег:
{ "tag": "FRAME", "class": "card", "children": [] }
{ "tag": "TEXT", "class": "label", "content": "Continue" }
{ "tag": "COMPONENT", "class": "chip" }
{
"tag": "INSTANCE",
"class": "cta",
"ref": { "component": "WButton", "properties": { "variant": "primary" } }
}
{
"tag": "ICON",
"class": "leading-icon",
"ref": { "component": "circle-info" }
}
{ "tag": "SLOT", "class": "content-slot", "slot": { "default": "WButton" } }
{ "tag": "RECTANGLE", "class": "bg" }
{ "tag": "ELLIPSE", "class": "avatar-mask" }
{ "tag": "LINE", "class": "divider" }
{
"tag": "VECTOR",
"class": "body-shape",
"vectorPaths": [
{ "data": "M 0 140 L 564 140 L 540 0 L 24 0 Z", "windingRule": "NONZERO" }
],
"viewBox": [0, 0, 564, 140]
}
Используйте VECTOR, когда фигура не является прямоугольником, эллипсом или линией — скошенное тело кнопки, волна, вырез вкладки, кастомный бейдж. Он принимает SVG-подобные vectorPaths или Figma-подобный vectorNetwork для редактируемых вершин и получает те же визуальные стили, что и другие фигуры. Скругление вершин можно токенизировать из styles через vectorCornerRadii.
Паттерн скошенной кнопки
Держите редактируемую геометрию фигуры в structure, а цвет/размер/скругление управляйте из styles. Середина остаётся обычным auto-layout FRAME, поэтому текст и иконки ведут себя как обычно.
{
"tag": "FRAME",
"class": "wrap",
"children": [
{
"tag": "VECTOR",
"class": "left-shape",
"vectorNetwork": {
"vertices": [
{ "x": 18, "y": 56 },
{ "x": 0, "y": 56 },
{ "x": 10, "y": 0 },
{ "x": 18, "y": 0 }
],
"segments": [
{ "start": 0, "end": 1 },
{ "start": 1, "end": 2 },
{ "start": 2, "end": 3 },
{ "start": 3, "end": 0 }
],
"regions": [{ "windingRule": "NONZERO", "loops": [[0, 1, 2, 3]] }]
}
},
{
"tag": "FRAME",
"class": "content",
"children": [{ "tag": "TEXT", "class": "label", "content": "PRIMARY" }]
}
]
}
".left-shape": { "widthType": "fixed", "heightType": "fixed", "width": 18, "height": 56, "fill": "{button.bg.accent}", "vectorCornerRadii": [0, "{radius.md}", "{radius.md}", 0] }
vectorCornerRadii принимает массив (в порядке вершин) или объект ({ "1": "{radius.md}" }). Индексы вершин с нуля. VECTOR — не импорт «сырого» SVG: кривые, скос и вырезы кладите в vectorPaths / vectorNetwork.
Режимы и правила SLOT
У SLOT строгие режимы: reference mode (конфиг slot с опциональным шаблоном ref) или children mode (инлайновые children). Reference-конфиг нельзя сочетать с непустыми children. Для пустого слота без reference-конфига укажите children: []; отсутствие обеих форм недопустимо.
{
"tag": "SLOT",
"class": "content-slot",
"slot": { "default": "WButton", "preferred": ["WButton", "WChip"] },
"ref": { "component": "WButton", "properties": { "size": "md" } }
}
{
"tag": "SLOT",
"class": "rows",
"children": [{ "tag": "FRAME", "class": "row", "children": [] }]
}
В разрабатываемой версии явное children: [] задаёт пустой слот; отсутствие и массива, и ссылки по-прежнему недопустимо.
Ограничения: у SLOT не может быть одновременно slot и непустых children; SLOT.ref валиден только в reference mode. SLOT ровно с одним дочерним INSTANCE (с ref, без slot) трактуется как сокращение reference mode.
Примечание Канонический и переносимый между схемами формат для slot.preferred и componentProperties.*.preferred — массив имён компонентов. Apply также принимает записи из Figma API, например { "type": "COMPONENT_SET", "key": "..." }; дополнительная отображаемая метка вроде component игнорируется. Используйте имена компонентов, если файл должен проходить внешнюю схему, разрешающую только строки.
ref для INSTANCE / SLOT
| Поле | Что делает | Пример |
|---|---|---|
component | Имя целевого компонента (обязательно при наличии ref) | "component": "WButton" |
library | Имя публикуемой библиотеки (исключает локальные компоненты) | "library": "SXL DS" |
key | Ключ опубликованного компонента (высший приоритет) | "key": "abc123" |
properties | Примитивные значения свойств инстанса | "properties": { "state": "active" } |
styles | Стили корня referenced-инстанса | "styles": { "width": "{size.icon}" } |
iconBindProperty | Явное имя свойства для icon swap | "iconBindProperty": "icon" |
icon | Дескриптор вложенного icon swap | "icon": { "component": "fire-3" } |
overrides | Переопределения содержимого детей | "overrides": { "label": "Apply" } |
slots | Переопределения слотов у referenced-инстанса | см. ниже |
nested | Обновления вложенных инстансов по имени слоя | см. ниже |
description | Опциональная заметка | "description": "Primary CTA" |
key имеет приоритет: компонент импортируется по ключу публикации, даже если локально есть компонент с таким же именем. Если импорт не удался, Apply сообщает о неразрешённой ссылке вместо подстановки локального компонента.
library исключает локальные компоненты. Поиск сначала использует ключ, запомненный для этой библиотеки, затем другие сохранённые ключи опубликованных компонентов и удалённые компоненты, уже используемые на канвасе. Если в сохранённых источниках несколько одноимённых компонентов, приоритет получает совпадающее имя библиотеки; неустранимая неоднозначность вызывает предупреждение с просьбой указать key. Регистр, пробелы и знаки в имени библиотеки не учитываются: "Design Library: Core" и "Design Library Core" совпадают. Имена компонентов остаются точными. Без сведений об источнике публикации имя библиотеки служит подсказкой для поиска; для точной идентичности используйте key. Одного включения библиотеки может быть недостаточно для обнаружения её компонентов через Plugin API Figma. Те же правила действуют для ref.nested.<слой>.component и замены иконок. Без library и key доступен локальный поиск по имени.
Если композиция создаёт компонент, Apply проверяет трекнутые ссылки инстансов и при неизменённой композиции. Если локальный мастер удалён, Apply может перепривязать существующий инстанс к единственному живому компоненту, для которого сохранённый трекинг подтверждает тот же JSON-источник. Одного совпадения имени недостаточно. Рабочие связи и ручные замены компонентов сохраняются; ограничения key/library продолжают действовать. Если источник, цель или сохранность ручного содержимого нельзя проверить, Apply сообщает о незавершённом прогоне и не угадывает замену. Ручные переопределения свойств компонента пока требуют ручного восстановления. Для потерянных default-инстансов слота со свойствами из ref.properties автоматическое восстановление тоже пока недоступно. Новый способ выбора по локальному ID ноды в JSON не добавляется.
ref.styles относится к материализованному корню referenced INSTANCE. Используйте его, когда самому default-инстансу нужны layout- или paint-bindings, например binding размера иконки внутри SLOT. Поддерживаются те же aliases и token refs, что и в обычных styles: widthType, heightType, width, height, fill, color.
Style-блок host по-прежнему стилизует сам composition node. В reference-mode слотах используйте refStyles внутри обычного style-блока, если варианты должны переопределять referenced root. refStyles мержится поверх ref.styles и применяется только пока текущий slot child совпадает с ref.component; он не каскадится на preferred/user swap вроде WBadge.
{
"tag": "SLOT",
"class": "leading-slot",
"slot": { "default": "circle-info", "preferred": ["circle-info", "WBadge"] },
"ref": {
"component": "circle-info",
"properties": { "style": "filled" },
"styles": {
"widthType": "fixed",
"heightType": "fixed",
"width": "{sz.fixed.reg.xs}",
"height": "{sz.fixed.reg.xs}"
}
}
}
"styles": {
"$placement=inner .leading-slot": {
"refStyles": {
"width": "{sz.fixed.reg.3xs}",
"height": "{sz.fixed.reg.3xs}"
}
}
}
Значения слота по вариантам
Доступно с версии 2.9.4.
Слот в reference-режиме получает ссылку на default-инстанс после применения каскада стилей варианта. Это работает для SLOT.ref вместе с slot.default и для сокращённой записи с единственным дочерним INSTANCE. Для того же referenced-компонента properties и styles объединяются по отдельным ключам. Изменение component, key или library начинает новую ссылку: укажите идентичность цели и нужные ей свойства и стили заново, чтобы не наследовать настройки прежнего компонента.
Например, Modal может использовать компактный заголовок, а Drawer — широкий. Задайте default-инстанс в слоте заголовка, затем его свойства и стили корня для каждого варианта родителя. Layout родителя задавайте в его style-блоке, а оформление самого заголовка — в стилях referenced-инстанса.
Generate, Apply и генерация из шаблона используют получившийся default. Apply обновляет существующий default-инстанс на месте, только если может подтвердить, что это ранее заданное автором содержимое. Ручная замена инстанса, изменение заданных автором свойств, произвольные дочерние ноды или пустой слот сохраняются с предупреждением. Прерванное обновление можно повторить; Apply не сбрасывает слот с потерей пользовательского содержимого.
Настройки Edit slot
Задавайте description, preferred и slotSettings в записи SLOT внутри componentProperties, а не в structure.slot. Настройки одинаковы для reference-слотов, сокращённой записи с одним инстансом и слотов с дочерними нодами. Для намеренно пустого слота требуется явное children: []; один SLOT без содержимого и настроенной ссылки недопустим.
{
"$type": "composition",
"name": "Panel",
"componentProperties": {
"content": {
"type": "SLOT",
"layer": "content",
"description": "Panel content",
"preferred": ["Header"],
"slotSettings": {
"stretchChildOnInsert": true,
"displayEmptyByDefault": false,
"minChildren": 0,
"maxChildren": null,
"allowPreferredValuesOnly": false
}
}
},
"structure": {
"tag": "FRAME",
"class": "root",
"children": [{ "tag": "SLOT", "class": "content", "children": [] }]
},
"styles": { ".root": { "direction": "column", "width": 320, "height": 240 } }
}
Чтобы вставить default-инстанс, замените пустой массив слота на [{ "tag": "INSTANCE", "class": "header", "ref": { "component": "Header" } }]. Default-содержимое задаётся в структуре; componentProperties.content.defaultValue — необязательное legacy-поле, а не настройка Edit slot.
Три логические настройки принимают только JSON boolean. minChildren и maxChildren принимают целые неотрицательные числа или null для снятия ограничения; если оба значения числовые, минимум не должен превышать максимум. preferred: [] явно очищает список предпочтительных компонентов. Неизвестные имена настроек отклоняются. Это подсказки вставки и рекомендательные ограничения Figma, а не разрешение удалить существующее содержимое или правило разрушительной проверки. См. Figma SlotSettings.
Плагин сохраняет эти настройки в Figma. Автоматическое растягивание зависит от поведения текущей версии Figma и может не применяться при включённом stretchChildOnInsert. displayEmptyByDefault управляет подсветкой пустого слота, а не удаляет его default-содержимое.
В стилях варианта задавайте ref на селекторе SLOT.class для прямого reference-слота или на объявленном классе дочернего инстанса для сокращённой записи. ref на неявном пути, построенном из имени default-компонента, игнорируется с предупреждением для защиты пользовательских изменений. Обычные стили ширины, цвета и layout по этому пути по-прежнему допустимы.
Ключ JSON внутри componentProperties задаёт авторское имя свойства. Для переименования измените этот ключ, сохранив layer и сам слой слота в structure. Например, замените content на body:
"componentProperties": {
"body": {
"type": "SLOT",
"layer": "content",
"description": "Panel content"
}
}
Apply переименовывает существующее нативное свойство и сохраняет его идентификатор, привязку и содержимое слота. Имя слоя в structure менять для этого не нужно. Неизменённый JSON сохраняет имя, заданное вручную в Figma. Конфликт имён или неподтверждённая привязка вызывают диагностику и оставляют Apply незавершённым.
Если другая композиция обращается к этому слоту через ref.slots или ref.nested, обновите ссылки со старым именем слота или полным ключом свойства. Такие ссылки выбирают текущее нативное свойство; переименование его владельца не переписывает другие JSON-файлы.
При обновлении старой композиции: сначала выполните Apply с прежним JSON, чтобы плагин запомнил авторское имя, затем измените ключ свойства. При первом сопоставлении старой композиции плагин сохраняет текущее имя Figma: без истории он не может отличить ручное переименование от правки JSON. Описание, предпочтительные компоненты и настройки можно обновлять независимо от имени.
Get Code сохраняет в JSON описание, настройки слота и опубликованные ключи предпочтительных компонентов. Физически пустой слот экспортируется как children: []; скрытые default-ноды сохраняют visible: false.
Нативные слоты внутри вложенных инстансов
Доступно с версии 2.9.4.
ref.nested.<метка>.slots задаёт один default-компонент в нативном слоте вложенного инстанса. Имя слоя целевого инстанса должно совпадать точно и быть уникальным. При повторяющихся именах задайте path — массив точных имён прямых дочерних слоёв относительно корня referenced-инстанса. Тогда метка обозначает блок JSON, а path выбирает инстанс.
"nested": {
"field": {
"path": ["body", "field"],
"slots": {
"leadingSlot": {
"component": "PhonePrefix",
"properties": { "size": "lg" }
}
}
}
}
Слот выбирается по точному ключу нативного свойства, его уникальному имени либо уникальному имени слоя, если такого свойства нет. Полный ключ свойства различает одинаковые названия. Обычные фреймы и слоты другого вложенного инстанса не подставляются. Для опубликованного содержимого также можно указать library и key.
Вложенный путь поддерживает один компонент либо эквивалентную запись nodes: [{ "component": "PhonePrefix" }]. Явное {"op":"replace","nodes":[]} очищает только содержимое, для которого подтверждено соответствие авторскому default. Отсутствие slots или конкретного слота сохраняет содержимое. Ручные замены, изменённые свойства, пользовательское содержимое, скрытые слои и вручную опустошённые слоты сохраняются. Первичное сопоставление требует совпадения с default-слотом основного компонента. Оно проверяется до изменения BOOLEAN-свойств видимости тем же блоком и повторно перед обновлением содержимого. Ранее изменённый слот не становится авторским default только потому, что последующее изменение свойства сделало его похожим на исходный. Недоступный компонент или неоднозначная цель дают диагностику и оставляют прогон незавершённым.
append, patch, несколько детей, name и icon у ребёнка в этом вложенном контракте не поддерживаются и вызывают ошибки валидации. Контракт верхнеуровневого ref.slots отдельный. Вложенные слоты применяются после свойств родителя. Generate строит затронутые варианты по отдельности; Apply обновляет существующие ноды на месте. Нативные слоты не отсоединяются, не клонируются в обычные фреймы, не сбрасываются и не назначаются через setProperties.
Ключи опубликованных наборов компонентов
ref.key принимает ключ опубликованного компонента или набора компонентов. Для набора вариант выбирается по объявленным вариантным осям в properties; без селекторов используется вариант набора по умолчанию. Ключ конкретного компонента сохраняет этот компонент, пока селекторы не запросят другой точный вариант того же набора; оси, отсутствующие в properties, сохраняют значения компонента по ключу. Текстовые, булевы и остальные свойства инстанса применяются отдельно. Недопустимая или неоднозначная комбинация вариантов даёт диагностику и не приводит к подстановке одноимённого компонента. Сохранённые ключи и ключи леджера используют ту же цепочку импорта компонентов и наборов.
Вложенные ссылки и переопределения слотов
ref.nested — адресует вложенные инстансы по имени слоя. component меняет сам вложенный инстанс на другой компонент (как ручная замена инстанса в Figma), properties задаёт его свойства, icon меняет иконку внутри него:
"nested": {
"Badge": {
"properties": { "label": "3" },
"icon": { "component": "fire-3", "properties": { "style": "filled" } }
},
"icon": { "component": "casino-games" }
}
ref.slots — переопределяет слоты внутри referenced-инстанса (op: replace, append, patch):
"slots": {
"footer": {
"op": "replace",
"nodes": [{ "component": "WButton", "name": "apply", "properties": { "variant": "primary" } }]
}
}
Вариант набора может оборачивать другой существующий компонент, переопределяя ref из вариантного селектора, — тогда одна нода INSTANCE обслуживает весь wrapper-набор:
"styles": {
"$item=neutral-secondary-sm .item": {
"ref": { "component": "WButton", "properties": { "style": "neutral", "variant": "secondary", "size": "sm" } }
}
}
Props
| Ключ | Тип | Что делает |
|---|---|---|
props | Record<string, (string | boolean | number)[]> | Объявляет каждую ось вариантов и её допустимые значения |
states | string[] | Имена осей из props, которые являются взаимоисключающими состояниями |
props объявляет все допустимые значения для каждой оси; каждая комбинация становится одним сгенерированным вариантом.
"props": {
"size": ["sm", "md", "lg"],
"state": ["default", "hover", "active"],
"compact": [true, false]
}
Поведение: при сопоставлении селекторов значения сравниваются как строки; ось с default использует его как fallback для отсутствующих правил, иначе fallback — первое значение. Держите число осей осознанным — комбинации растут мультипликативно.
states (взаимоисключающие оси)
states перечисляет оси из props, которые ведут себя как взаимоисключающие UI-состояния (:disabled, :loading), а не как комбинируемые варианты. Они исключаются из декартова произведения — каждое не-дефолтное значение добавляет один вариант поверх дефолтов.
"props": {
"_state": ["default", "hover", "focus", "select"],
"loading": ["false", "true"],
"disabled": ["false", "true"]
},
"states": ["loading", "disabled"]
Без states это матрица 4 × 2 × 2 = 16 вариантов; со states она схлопывается до 6 (4 интерактивных + loading=true + disabled=true). Правила: off-значение — первое значение оси; каждое имя в states должно быть в props; Apply декларативно пере-схлопывает существующий набор. Обычные визуальные выборы (size, variant, tone, theme) держите только в props.
Маппинг в codegen:
- React / Vue: ось, значения которой ровно
false/true, становитсяboolean-пропом (напр.loading?: boolean); - DivKit: наборы эмитятся через
card.states/state_id;statesлишь управляет тем, какие варианты материализуются, и не создаёт фиктивныйcustom_type; - ось с префиксом
_(напр._state) — внутренняя ось interaction-состояния, исключена из публичного codegen-API (её ведёт собственный:hover/:focusCSS потребителя); не-_оси вродеloading/disabledэмитятся как реальные пропсы.
Расширенная объектная форма с combineWith (скрещивание состояния со структурными осями) зарезервирована, но пока не реализована — её использование вызывает ошибку валидации.
Component Properties
componentProperties задаёт нативные свойства Figma, доступные на инстансах.
| Тип | На что указывает layer | defaultValue |
|---|---|---|
TEXT | class в structure | Обязателен; авто-выводится из content, если опущен |
BOOLEAN | class в structure | Обязателен; по умолчанию true, если опущен |
INSTANCE_SWAP | class в structure | Обязателен |
SLOT | class в structure | Необязательное legacy-поле; default-содержимое берётся из структуры |
NESTED_INSTANCE | Имя слоя вложенного инстанса (не class) | Не обязателен |
Пример на каждый тип:
"title": { "type": "TEXT", "layer": "title", "defaultValue": "Promo title" }
"showMeta": { "type": "BOOLEAN", "layer": "meta", "defaultValue": true }
"icon": { "type": "INSTANCE_SWAP", "layer": "icon", "defaultValue": "circle-info", "preferred": ["circle-info", "star"] }
"media": { "type": "SLOT", "layer": "media-slot", "defaultValue": "WImageTile" }
"badge": { "type": "NESTED_INSTANCE", "layer": "Badge" }
Правило layer: для TEXT, BOOLEAN, INSTANCE_SWAP, SLOT используйте class из structure; для NESTED_INSTANCE — реальное имя вложенного слоя в Figma.
Свойства внутри слотов: Figma не поддерживает привязку свойства внешнего компонента к обычному слою внутри нативного слота. Например, текстовый слой title внутри слота может сохранять текст и стили, но не может быть целью внешнего componentProperties.title. Удалите объявление этого свойства, сохранив текстовый слой на месте. Вложенный инстанс компонента может сохранять свойства, заданные в его собственном мастере. Свойство видимости на самом слоте также поддерживается. См. документацию Figma о слотах.
С версии 2.9.4 валидация предупреждает о такой привязке через границу слота, а Generate / Apply отклоняет её до изменения структуры. Если обязательная привязка не удалась или не подтверждается повторным чтением, прогон остаётся незавершённым вместо сообщения об успешном Apply.
Styles
styles управляет оформлением слоя, layout и переопределениями по вариантам. Ключ — это CSS-подобный класс (.card), легаси-класс (card) или вариантный селектор ($state=hover .card); блоки можно вкладывать. Каждое свойство ниже показано с точным написанием в JSON, сгруппировано по назначению:
| Группа | Что покрывает |
|---|---|
| Контейнер & auto-layout | direction, justifyContent, alignItems, gap, padding, … |
| Размер слоя | widthType, width, min/maxWidth, aspectRatio, … |
| Позиционирование | position, top/right/bottom/left, x/y, alignSelf, … |
| Grid | rows/columns, rowGap/columnGap, выравнивание/спан grid-ребёнка |
| Фон/заливка/цвет | background, fill, color |
| Границы & углы | border, borderRadius, strokeAlign, cornerSmooth, … |
| Тени & эффекты | boxShadow, backgroundBlur, layerBlur, opacity, glass |
| Типографика | fontFamily, fontSize, lineHeight, textCase, … |
| Видимость & сброс | visible, display, очистка через none |
| Управление инстансом | component, instanceProperties, nestedInstanceProperties, ref |
| Переменные & метаданные | explicitVariableModes, layoutGrids, exportSettings, mask |
"styles": {
".root": { "direction": "row", "gap": 8, "padding": "12 16" },
"$state=hover .root": { "background": "{color.brand.hover}" }
}
Селекторы и каскад
- сначала применяются базовые стили; затем совпавшие селекторы по специфичности (больше условий — выше приоритет);
- одинаковая специфичность → побеждает более позднее объявление в JSON;
- descendant-селекторы резолвятся через dot-пути (
.footer .item→footer.item); - когда класс повторяется в разных ветках, предпочитайте полный путь (
.header .item) вместо голого.item; - ключ селектора может содержать условия и классы в любом порядке (
"$state=hover .root"и".root $state=hover"равны), а вложенные блоки внутри него накапливают и условия, и классы:"$variant=secondary .root": { "background": "…", ".label": { "color": "…" } }даёт правила$variant=secondary .rootи$variant=secondary .root .label.
"styles": {
".wrap": {
"padding": 8,
".item": { "widthType": "fill" },
"$state=active": { ".item": { "opacity": 1 } }
}
}
Контейнер и auto-layout
| Свойство | Что делает — значения | Пример |
|---|---|---|
direction | Ось раскладки — row, column, grid, none | "direction": "row" |
justifyContent | Распределение по главной оси — start, center, end, space-between, space-evenly, space-around | "justifyContent": "space-between" |
alignItems | Выравнивание по поперечной оси — start, center, end, baseline | "alignItems": "center" |
alignContent | Распределение перенесённых рядов — auto, space-between | "alignContent": "space-between" |
flexWrap | Перенос — nowrap, wrap | "flexWrap": "wrap" |
gap | Отступ между детьми (px) | "gap": 12 |
wrapGap | Поперечный отступ между перенесёнными рядами — px или "auto" (повторяет gap) | "wrapGap": 8 |
padding | Внутренние отступы — число, "T R B L" или "none" | "padding": "16 16 20 16" |
overflow | Обрезка — hidden, clip, visible, auto, scroll | "overflow": "hidden" |
primaryAxisSizingMode | Размер контейнера по главной оси — auto, fixed | "primaryAxisSizingMode": "auto" |
counterAxisSizingMode | Размер контейнера по поперечной оси — auto, fixed | "counterAxisSizingMode": "fixed" |
boxSizing | Обводка учитывается в layout — border-box, content-box | "boxSizing": "border-box" |
canvasStacking | Z-порядок сиблингов — first-on-top, last-on-top | "canvasStacking": "first-on-top" |
".header": { "direction": "row", "justifyContent": "space-between", "alignItems": "center", "gap": 8, "padding": "12 16" }
flexWrap, alignContent и wrapGap описывают ряд с переносом, поэтому нужен "direction": "row".
"wrapGap": "auto" оставляет отступ между рядами равным gap; число задаёт его отдельно. Если у слоя
остался direction, но из полного листа стилей пропали flexWrap, wrapGap или alignContent, Apply
возвращает значения по умолчанию Figma: без переноса, отступ снова равен gap, распределение auto.
".tags": {
"direction": "row",
"flexWrap": "wrap",
"widthType": "hug",
"maxWidth": 1276,
"gap": 10,
"wrapGap": "auto",
"alignContent": "space-between"
}
Размер слоя
| Свойство | Что делает — значения | Пример |
|---|---|---|
widthType / heightType | Режим размера — fixed, hug, fill | "widthType": "fill" |
width / height | Фиксированный размер (px) | "width": 360 |
scale | Абсолютный масштаб явного INSTANCE / ICON от 0.01 до 100; ограничения | "scale": 1.03 |
minWidth / maxWidth | Границы ширины (px) | "maxWidth": 480 |
minHeight / maxHeight | Границы высоты (px) | "minHeight": 40 |
aspectRatio | Фиксация пропорции — "1/1", или "none" / false для сброса | "aspectRatio": "1/1" |
constrainProportions | Блокировка пропорций ширина/высота — true, false | "constrainProportions": true |
targetAspectRatio — принимаемый алиас aspectRatio.
Позиционирование
| Свойство | Что делает — значения | Пример |
|---|---|---|
position | Поток — relative, absolute, none | "position": "absolute" |
top / right / bottom / left | Absolute-инсеты (px) | "top": 8 |
x / y | Абсолютные координаты на канвасе (px) | "x": 24 |
alignSelf | Переопределение поперечного выравнивания — inherit, stretch, start, center, end | "alignSelf": "stretch" |
flexGrow | Коэффициент роста | "flexGrow": 1 |
rotate | Поворот (град) | "rotate": 45 |
constraintH / constraintV | Констрейнты Figma — left, center, right, scale, stretch | "constraintH": "center" |
".badge": { "position": "absolute", "top": 8, "right": 8 }
Grid-раскладка
Задаётся, когда direction равен grid. "display": "grid" означает то же самое, если direction не задан.
| Свойство | Что делает | Пример |
|---|---|---|
rows / columns | Число дорожек; можно не писать, если дорожки перечислены в шаблоне | "columns": 3 |
rowGap / columnGap | Отступы дорожек (px) | "columnGap": 8 |
gridTemplateRows / gridTemplateColumns | Размеры дорожек: fr, px, auto/hug (по содержимому), repeat(n, …); строка или массив | "gridTemplateColumns": "repeat(7, 1fr)" |
gridAutoFlow | Размещение — manual (по вашим ячейкам), row (в первую свободную, по порядку слоёв) | "gridAutoFlow": "manual" |
gridAutoRows | Figma сама добавляет и убирает строки вслед за детьми — true, false | "gridAutoRows": true |
gridRow / gridColumn | Ячейка grid-ребёнка, счёт с 1 | "gridRow": 2, "gridColumn": 4 |
justifySelf / gridAlignSelf | Выравнивание grid-ребёнка (H / V) — start, center, end, auto | "justifySelf": "center" |
gridRowSpan / gridColumnSpan | Растяжение grid-ребёнка (дорожки) | "gridColumnSpan": 2 |
repeat(7, 1fr) — это семь дорожек, поэтому columns можно не писать: число задаёт шаблон. Если написать
оба и они расходятся, Generate и Apply останавливаются до изменений на канвасе и называют слой и способ
исправить. Без gridRow / gridColumn дети заполняют сетку в порядке structure.
gridRow и gridColumn пишутся вместе на одном слое, и родителю нужен "gridAutoFlow": "manual" —
при "row" Figma расставляет детей сама. "gridAutoRows": true отдаёт число строк Figma, поэтому rows
и длина gridTemplateRows перестают действовать.
Если в JSON нет gridTemplateRows / gridTemplateColumns, Figma сохраняет текущие размеры дорожек и
следит за числом строк и колонок. Полный лист стилей с direction: "grid", но без rowGap / columnGap
возвращает отступы к 0.
Слой SLOT не может быть сеткой — Figma это запрещает. Поставьте "direction": "grid" на фрейм внутри слота.
".grid": {
"direction": "grid",
"gridTemplateColumns": "repeat(7, 1fr)",
"gridTemplateRows": "hug hug",
"columnGap": 12,
"rowGap": 12,
"widthType": "fill",
"heightType": "hug",
".card": { "widthType": "fill", "heightType": "hug" }
}
Колонкам в fr нужна ширина, которую они делят, поэтому самой сетке оставьте fill или fixed: при
hug делить нечего. В композиции с "component": false корень попадает прямо на страницу, и заполнять
ему нечего: задайте корню фиксированную ширину, а fill оставьте сетке внутри.
Фон, заливка, цвет
| Свойство | Что делает — значения | Пример |
|---|---|---|
background | Заливка контейнера — hex, градиент, массив слоёв или "none" / "transparent" | "background": "#FFFFFF" |
fill | Заливка фигуры / вектора / иконки — hex или токен, "none" очищает | "fill": "{button.primary}" |
color | CSS-подобный currentColor (текст + краска иконки) — hex или токен, "none" | "color": "{text.primary}" |
color — это CSS-подобный currentColor: на TEXT задаёт заливку текста; на ICON / иконках-инстансах перекрашивает глиф (fill-иконки получают заливку, stroke-иконки — обводку). Ссылки на токены сохраняют привязку к переменной; литеральные цвета её заменяют. fill и strokes — низкоуровневые: используйте их для заливок фигур или прямой обводки. Для stroke-only иконки "fill": "none" оставляет только краску обводки.
Градиентный фон:
".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)"
]
}
Границы, обводка, скругления
| Свойство | Что делает — значения | Пример |
|---|---|---|
border | Shorthand "<толщина> <стиль> <цвет>"; "none" убирает обводку | "border": "1px solid #E7EAF1" |
borderColor | Цвет обводки | "borderColor": "#E7EAF1" |
borderWidth | Толщина обводки (px) | "borderWidth": 1 |
borderStyle | Стиль обводки — solid, dashed | "borderStyle": "dashed" |
borderRadius | Радиус скругления (px) | "borderRadius": 20 |
cornerSmooth | Сглаживание углов 0–1 (squircle) | "cornerSmooth": 0.6 |
strokeAlign | Положение обводки — inside, center, outside | "strokeAlign": "inside" |
strokeCap / strokeJoin | Стиль конца / соединения линии | "strokeCap": "ROUND" |
strokeTopWeight … strokeLeftWeight | Толщина обводки по стороне (px) | "strokeTopWeight": 2 |
dashPattern | Пунктир — массив длин штрих/пропуск | "dashPattern": [4, 4] |
outline / outlineOffset | Внешняя обводка + отступ | "outline": "2px solid #0D6EFD" |
vectorCornerRadii | Радиусы по вершинам VECTOR (в порядке вершин) | "vectorCornerRadii": [0, 8, 8, 0] |
".card": { "border": "1px solid #E7EAF1", "borderRadius": 20 },
"$state=active .card": { "outline": "2px solid #0D6EFD", "outlineOffset": "2px" }
Тени и эффекты
| Свойство | Что делает — значения | Пример |
|---|---|---|
boxShadow | Тени — массив объектов теней, или "none" | см. блок |
backgroundBlur | Радиус размытия фона (px), или "none" | "backgroundBlur": 12 |
layerBlur | Радиус размытия слоя (px), или "none" | "layerBlur": 8 |
opacity | Прозрачность слоя 0–1, или "none" (= 1) | "opacity": 0.56 |
blendMode | Режим наложения слоя: normal, multiply, screen, overlay, darken, lighten, … | "blendMode": "multiply" |
glass | Эффект стекла, или "none" для сброса | "glass": "none" |
effects | Полный композит эффектов (алиас токена или слои) | "effects": "{shadow.md}" |
".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)" }] }
Типографика
Задаётся на ноде TEXT.
| Свойство | Что делает — значения | Пример |
|---|---|---|
fontFamily | Семейство шрифта | "fontFamily": "Inter" |
fontWeight | Насыщенность | "fontWeight": 600 |
fontSize | Размер (px) | "fontSize": 18 |
lineHeight | Межстрочный интервал | "lineHeight": "24px" |
letterSpacing | Трекинг | "letterSpacing": "0px" |
textAlign | По горизонтали — left, center, right, justify | "textAlign": "left" |
verticalAlign | По вертикали — top, center, middle, bottom | "verticalAlign": "center" |
textCase | Регистр — none, uppercase, lowercase, capitalize, small-caps | "textCase": "uppercase" |
textDecoration | Оформление — none, underline, line-through | "textDecoration": "underline" |
textSizing | Авто-размер — fixed, height-auto, auto, truncate | "textSizing": "height-auto" |
textTruncation | Обрезка — disabled, ending (многоточие) | "textTruncation": "ending" |
maxLines | Максимум строк до обрезки | "maxLines": 2 |
paragraphSpacing | Отступ между абзацами (px) | "paragraphSpacing": 8 |
paragraphIndent | Отступ первой строки (px) | "paragraphIndent": 16 |
leadingTrim / verticalTrim | Обрезка leading строки — CAP_HEIGHT, NONE | "leadingTrim": "CAP_HEIGHT" |
".title": { "fontFamily": "Inter", "fontWeight": 600, "fontSize": 18, "lineHeight": "24px", "textAlign": "left", "textSizing": "height-auto" }
Композитный ключ typography / font применяет полный текстовый стиль сразу.
Видимость и сброс
visible: false— слой остаётся в варианте, но скрыт в Figma;display: "none"— ветка исключается из собранного варианта;- если заданы оба, побеждает
display: "none". Когда более поздний селектор снова показывает ветку, укажите нужные ей размеры/layout; - любое другое значение
displayпоказывает ветку, а"grid"вдобавок включает grid-раскладку, еслиdirectionне задан.
"$loading=true": {
".button": {
".label": { "visible": false },
".spinner": { "display": "flex", "visible": true, "width": "{button.size.icon}" }
}
}
CSS-подобные очищающие значения (пишутся, чтобы сбросить свойство):
| Написание сброса | Эффект |
|---|---|
"background": "none" / "transparent" | Очищает заливки |
"color": "none" / "fill": "none" | Очищает текст/currentColor/краску |
"boxShadow": "none" / "effects": "none" | Очищает эффекты |
"padding": "none" | Все паддинги 0 |
"opacity": "none" | Резолвится в 1 |
"mask": "none" / false | Снимает маску |
"aspectRatio": "none" / false | Разблокирует пропорцию |
Управление инстансом
Задаётся на ноде INSTANCE.
| Свойство | Что делает | Пример |
|---|---|---|
component | Смена main-компонента (swap по имени) | "component": "WButton" |
instanceProperties / properties | Значения свойств инстанса | см. блок |
nestedInstanceProperties | Свойства вложенных инстансов по имени слоя | см. блок |
ref | Полное переопределение цели инстанса | см. раздел ref |
".action-main": { "component": "WButton", "instanceProperties": { "variant": "primary", "size": "md", "label": "Continue" } }
".badge": { "nestedInstanceProperties": { "Badge": { "label": "2" } } }
Режимы переменных и Figma-метаданные
| Свойство | Что делает | Пример |
|---|---|---|
explicitVariableModes | Принудительный режим переменной по коллекции | "explicitVariableModes": { "Themes": "Dark" } |
variableModes | Алиас explicitVariableModes | "variableModes": { "Themes": "Dark" } |
layoutGrids | Массив layout-сеток Figma | см. блок |
exportSettings | Массив настроек экспорта Figma | см. блок |
mask / maskType | Маска слоя — alpha, vector, luminance, none | "mask": "alpha" |
".root": {
"explicitVariableModes": { "Themes": "Dark" },
"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 } }]
}
Сбросить явный режим для коллекции можно через null, false, "none", "auto" или "unset".
Transitions
Прототип-переходы задаются на корне composition (shorthand или объектная форма), но никогда как ключ стиля слоя. Apply переписывает только реакции, созданные из transitions; реакции, добавленные в Figma вручную, остаются.
| Поле | Что делает | Значения |
|---|---|---|
trigger | Когда срабатывает | on-hover, on-click, on-press, on-drag, on-enter, on-leave, mouse-up, mouse-down, after-timeout: 500 (в сокращённой записи: after-timeout 500 dissolve 300ms) |
animation | Тип перехода | smart-animate, dissolve, instant, scroll-animate, slide-in, slide-out, push, move-in, move-out |
duration | Длительность | напр. 200ms |
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(...) |
direction | Опциональное направление | left, right, top, bottom |
condition | Опциональное условие по переменной | { "variable": …, "op": "==", "value": … } |
Ключ правила задаёт, из какого варианта в какой ведёт переход: "$prop=a -> $prop=b" (можно несколько условий через пробел: "$variant=primary $disabled=false -> $variant=secondary"). Значения осей могут содержать дефисы и точки. Именованный ключ ("hover-in") допустим только в объектной форме с полями from и to.
Shorthand — порядок: trigger animation duration easing [direction]:
"transitions": { "$state=default -> $state=hover": "on-hover smart-animate 200ms ease-out" }
Объектная форма с условием (именованный ключ, направление задают from и to):
"transitions": {
"hover-in": {
"from": "$state=default",
"to": "$state=hover",
"trigger": "on-hover",
"animation": "smart-animate",
"duration": "200ms",
"easing": "ease-out",
"direction": "right",
"condition": { "variable": "motion.enabled", "op": "==", "value": true }
}
}
Theme binding
themeBinding связывает ось варианта (обычно theme) с режимами коллекции переменных для корректного рендера светлой/тёмной темы.
| Поле | Что делает | Пример |
|---|---|---|
target | Метка цели привязки | "target": "root" |
prop | Имя оси из props | "prop": "theme" |
modes | Карта значение оси → конфиг режима | { "light": { "type": "local", "collection": "Themes", "mode": "Light" } } |
applyTo | Какие домены переключать | ["variables", "textStyles", "effectStyles"] |
"themeBinding": {
"target": "root",
"prop": "theme",
"modes": {
"light": { "type": "local", "collection": "Themes", "mode": "Light" },
"dark": { "type": "local", "collection": "Themes", "mode": "Dark" }
},
"applyTo": ["variables", "typography", "textStyles", "effectStyles", "paintStyles"]
}
prop должен существовать в props. У источника режима есть type (local / library), collection, mode и опциональный library. Каждый modes.<value> — это либо плоский источник, либо обёртка { primary, fallback[] }.
Advanced-поля
Встроенные size / style
Файл composition может встраивать DTCG-блоки токенов size и style для самодостаточных пакетов компонентов (неймспейс по имени компонента). Они опциональны и сами по себе на layout не влияют.
"size": { "md": { "height": "40px", "paddingX": "16px" } },
"style": { "primary": { "bg": "{color.brand.primary}" } }
Полный пример
Полный набор компонентов, затрагивающий большинство возможностей: многоосевые props, componentProperties, SLOT и INSTANCE, переопределения по селекторам, корневые transitions и themeBinding.
{
"$type": "composition",
"name": "WPromoCard",
"$description": "Промо-карточка с медиа-слотом, действием и состояниями.",
"props": {
"theme": ["light", "dark"],
"size": ["sm", "md"],
"state": ["default", "hover", "disabled"]
},
"states": ["disabled"],
"componentProperties": {
"title": {
"type": "TEXT",
"layer": "title",
"defaultValue": "Promo title"
},
"showMeta": { "type": "BOOLEAN", "layer": "meta", "defaultValue": true },
"media": {
"type": "SLOT",
"layer": "media-slot",
"defaultValue": "WImageTile"
}
},
"structure": {
"tag": "FRAME",
"class": "card",
"name": "Card",
"children": [
{
"tag": "SLOT",
"class": "media-slot",
"slot": { "default": "WImageTile" }
},
{ "tag": "TEXT", "class": "title", "content": "Promo title" },
{ "tag": "TEXT", "class": "meta", "content": "Meta" },
{
"tag": "INSTANCE",
"class": "cta",
"ref": {
"component": "WButton",
"properties": { "variant": "primary" }
}
}
]
},
"styles": {
".card": {
"direction": "column",
"gap": 12,
"padding": 16,
"background": "{card.bg}",
"borderRadius": 16,
"widthType": "fixed",
"width": 320
},
"$size=sm .card": { "width": 260 },
"$state=hover .card": {
"boxShadow": [
{
"x": 0,
"y": 12,
"blur": 32,
"spread": 0,
"color": "rgba(0,0,0,0.18)"
}
]
},
"$state=disabled .card": { "opacity": 0.56 },
".title": { "fontFamily": "Inter", "fontWeight": 600, "fontSize": 16 }
},
"transitions": {
"$state=default -> $state=hover": "on-hover smart-animate 160ms ease-out"
},
"themeBinding": {
"target": "root",
"prop": "theme",
"modes": {
"light": { "type": "local", "collection": "Themes", "mode": "Light" },
"dark": { "type": "local", "collection": "Themes", "mode": "Dark" }
},
"applyTo": ["variables", "textStyles", "effectStyles"]
}
}
Чеклист адаптации: замените имена компонентов (WImageTile, WButton) на имена из своей библиотеки; создайте коллекцию Themes или уберите themeBinding; начните с Generate, затем итерируйте Apply; держите name стабильным на нодах, адресуемых вложенно.
Для агентов и трансформеров
A) Figma → Composition JSON
- Задайте
$type: "composition"и стабильныйname. - Стройте
propsтолько из реальных осей вариантов; добавляйтеstatesдля взаимоисключающих условий. - Для каждой ноды пишите валидные
tagиclass; добавляйте стабильныйnameдля вложенно-адресуемых нод. - Для инстансов пишите
ref.componentи только валидные примитивныеref.properties. - Для слотов используйте reference mode (
slot) или children mode (children), никогда оба. - Все визуальные/layout-правила кладите в
styles(база + переопределения по селекторам). - Используйте корневые
transitionsиthemeBinding, а не хаки на уровне слоя. - Проверьте, что referenced-компоненты/токены существуют.
B) Composition JSON → React / Vue
- Читайте
structureкак дерево элементов; используйтеclass/path-ключи для резолва стилей. - Резолвьте вариантные селекторы (
$prop=value .class) против входящих пропсов. - Уважайте видимость веток (
display: "none"). - Трактуйте
ref.component→ импорт компонента,ref.properties→ передаваемые пропсы. - Трактуйте
componentProperties,themeBinding,slotHostPipelineи$figmaкак метаданные дизайн-тайма, если рантайм их не поддерживает. - Сохраняйте ссылки на токены (
{...}) или пре-резолвьте их через свой token-движок.
C) Pre-flight валидация
structureсуществует и у каждой ноды естьtag+class;stylesсуществует и содержит только объектные значения;themeBinding.propсуществует вprops;- нет удалённых легаси-ключей (
adapters,sizeStyles,colorStyles); - нет конфликта режимов SLOT (
slotс непустымиchildren).
Авторский ключ → runtime-ключ Figma (справка)
| Авторский ключ | Runtime-ключ |
|---|---|
direction | layoutMode |
justifyContent | primaryAxisAlignItems |
alignItems | counterAxisAlignItems |
alignContent | counterAxisAlignContent |
flexWrap | layoutWrap |
gap / wrapGap | itemSpacing / counterAxisSpacing |
widthType / heightType | layoutSizingHorizontal / Vertical |
alignSelf / flexGrow | layoutAlign / layoutGrow |
position | layoutPositioning |
top / right / bottom / left | insets → layout/position |
rows / columns | gridRowCount / gridColumnCount |
rowGap / columnGap | gridRowGap / gridColumnGap |
gridAutoFlow / gridAutoRows | gridItemsPositioning / gridAutoTracks |
gridRow / gridColumn | ячейка сетки (счёт с 1 → с 0) |
borderWidth / borderAlign | strokeWeight / strokeAlign |
borderRadius / cornerSmooth | cornerRadius / cornerSmoothing |
textSizing | textAutoResize |
textAlign / verticalAlign | textAlignHorizontal / Vertical |
rotate | rotation |
boxSizing / canvasStacking | strokesIncludedInLayout / itemReverseZIndex |
justifySelf / gridAlignSelf | gridChildHorizontalAlign / VerticalAlign |
Авторское значение → enum Figma (справка)
| Свойство | Автор → enum |
|---|---|
direction | row/column/none/grid → HORIZONTAL/VERTICAL/NONE/GRID |
justifyContent | start/center/end/space-between/space-evenly/space-around → MIN/CENTER/MAX/SPACE_BETWEEN/SPACE_EVENLY/SPACE_AROUND |
alignItems | start/center/end/baseline → MIN/CENTER/MAX/BASELINE |
widthType / heightType | fixed/hug/fill (алиасы: auto→HUG, stretch→FILL) → FIXED/HUG/FILL |
position | relative/absolute/none → AUTO/ABSOLUTE/AUTO |
textSizing | fixed/height-auto/auto/truncate → NONE/HEIGHT/WIDTH_AND_HEIGHT; truncate = NONE + textTruncation: ENDING |
textCase | none/uppercase/lowercase/capitalize/small-caps → ORIGINAL/UPPER/LOWER/TITLE/SMALL_CAPS |
strokeAlign | inside/center/outside → INSIDE/CENTER/OUTSIDE |
mask | alpha/vector/luminance/none → ALPHA/VECTOR/LUMINANCE или isMask:false |
Примечания: ссылки на токены ({...}) сохраняются и резолвятся во время apply; числовые строки и строки с единицами (px, rem, deg) нормализуются в числа там, где нужно.
Ограничения и предупреждения
- Неподдерживаемые сочетания свойств/нод игнорируются.
- Внутренности сторонних/библиотечных инстансов могут быть частично защищены Figma.
NESTED_INSTANCEиref.nestedзависят от стабильных имён слоёв.- Большие матрицы
propsдают тяжёлое число вариантов — где уместно, применяйтеstates. - Generate/Apply в Figma тихо пропускает неизвестные ключи стилей, не блокируя документированные свойства и алиасы. Оставляйте unknown keys только если их использует другой output, например code generation.
- Удалённые легаси-ключи дают ошибку парсинга:
adapters,sizeStyles,colorStyles.component: falseнесовместим с непустымиprops.
Связанные страницы
Варианты с привязкой к переменной
Вариантные секции стилей с блоком ref ("$state=hover .cell .envelope": { "ref": { "properties": { "_state": "hover" } } }) переопределяют только названные свойства: привязка {token} из структурного ref остаётся, и после всех стадий Apply каждая такая привязка проверяется — её потеря возвращается ошибкой, а не успехом.
Прямое свойство VARIANT явно объявленного инстанса может использовать экспортированный STRING-токен. Generate выбирает исходный вариант и привязывает свойство к переменной; Apply использует тот же контракт. Значение, разрешённое для активного режима инстанса, должно точно совпадать с допустимым значением варианта. Другие режимы могут содержать значения, которых нет среди вариантов; для зависимостей aliases сохраняется проверка корректных STRING-значений и идентичности. Отсутствующая идентичность, другой тип переменной, цикл или недопустимое значение дают диагностику вместо подстановки литерала. Идентичность переменной берётся из леджеров рабочей области, а не по совпадению отображаемого имени: сначала из леджера активного файла, затем из соседних diff-id.*.json того же Local Workspace — например, когда STRING экспортирован в библиотеку Core, а композиция собирается в файле компонентов. Внешняя переменная импортируется по опубликованному ключу и проверяется по возвращённому ключу и типу; библиотека должна быть опубликована и доступна. Один и тот же путь токена, экспортированный в несколько файлов, даёт диагностику о конфликте идентичностей.
{
"ref": {
"component": "ResponsiveContent",
"properties": {
"state": "default",
"breakpoint": "{breakpoint}"
}
}
}
Например, общий токен может содержать desktop, tablet и mobile, а инстанс — только варианты desktop и mobile. В Desktop или Mobile он привязывается напрямую, без дублирующего токена и Tablet-варианта. Если для инстанса разрешается tablet, Apply сообщает об ошибке и не подставляет другой вариант. Последующее переключение в неподдерживаемый режим в Figma также остаётся неподдерживаемым. Generate выбирает исходный вариант по значению, разрешённому для места размещения (унаследованный или явный режим родительского слоя), а без такого контекста — по режиму коллекции по умолчанию; это значение должно быть среди вариантов, иначе диагностика называет фактическое значение. Перед привязкой проверяется значение для фактического инстанса. Внешняя карточка при этом предоставляет только state, а вложенный инстанс следует унаследованному режиму. Литеральные свойства сохраняют прежнее поведение. Синтаксис привязывает прямое свойство VARIANT; одноимённое свойство среди произвольных потомков не ищется.
Повторный Apply сохраняет исправную привязку. Smart восстанавливает привязки на подтверждённых авторских слоях. Удаление авторской токенной привязки возвращает сохранённый предыдущий литерал, только пока присутствует именно управляемый alias; ручная замена сохраняется. Для scale, ограничений размеров и ручного содержимого продолжают действовать отдельные правила владения.
Чтобы снять старый явный режим импортированной коллекции, укажите её точно:
{
"variableModes": {
"Breakpoints": {
"type": "library",
"key": "<collection-key>",
"library": "<library-name>",
"mode": "auto"
}
}
}
key — ключ коллекции, а не переменной. auto снимает явный режим только этого слоя и возвращает наследование, не сбрасывая режим родителя. Задайте стиль каждому слою с нежелательным переопределением. library — необязательная проверка принадлежности, требующая доступной библиотеки. Одноимённая локальная коллекция не подставляется. Прежние строковые селекторы вроде "Themes": "Dark" продолжают выбирать локальные коллекции.
Миграция матрицы вариантов
Apply обновляет существующий ComponentSet на месте и при изменении числа вариантов или набора осей: набор и сохраняемые главные компоненты не пересоздаются, их идентификаторы и связи экземпляров остаются. Сопоставление существующих вариантов с новыми строится автоматически: сначала точные ключи, затем варианты, совпадающие по значениям общих осей (удалена или добавлена ось, изменены оба), а при нескольких кандидатах на один новый вариант — детерминированный выбор: вариант по умолчанию набора, затем порядок в наборе. Выбранная карта выводится в предупреждениях результата. Новые ключи собираются обычным путём, добавление значений на прежних осях идёт обычным путём добавления вариантов, а переименование осей и значений при том же числе вариантов — прежним путём переименования. Изменения структуры, свойств и стилей применяются в том же Apply.
Экземпляры удаляемых вариантов в документе переводятся на сохраняемый вариант с теми же значениями общих осей нативной заменой компонента — до любого изменения главных компонентов, пока набор ещё имеет прежние оси. До замены фиксируются только реальные переопределения экземпляра: по нативным данным о переопределениях и отличию от объявленного значения по умолчанию (свойства компонента: текст, булевы, замены инстансов, привязки переменных) и по прямым переопределениям вложенных слоёв (текст слоя, заменённый вложенный инстанс, видимость), адресуемым по пути слоя. Унаследованные значения по умолчанию не считаются переопределениями и после Apply следуют новому JSON. После замены каждое реальное переопределение читается обратно: неизменное — сохранено, изменённое нативной заменой — записывается обратно и проверяется. Удалённая ось, свойство или слой, которых нет у сохраняемого варианта, — ожидаемая потеря по JSON, она перечисляется в предупреждениях. Совместимое переопределение, которое не удалось сохранить или проверить, останавливает миграцию до удаления исходного варианта: результат неуспешный, трекинг не меняется, ничего не удалено. Эта проверка выполняется дважды: сразу после замены и заново после обновления главных компонентов, перед удалением исходных вариантов, по сохранённому снимку — кешированный результат не используется. Содержимое слотов следует эвристикам замены Figma и не проверяется чтением. Если удаляется значение оси (например, tablet), совместимого варианта у его экземпляров нет: Apply останавливается до записей и подсказывает ближайший сохраняемый компонент для явного поля consumers, либо переключите такие экземпляры вручную. Опубликованные варианты удаляются только с явной картой ниже: Plugin API не видит их экземпляры в других файлах, такие экземпляры продолжают отображаться, но теряют связь с библиотекой после публикации удаления. Если общих осей нет, требуется явная карта: ошибка содержит готовый позиционный вариант, который можно вставить и отредактировать; сохраняемый компонент не должен противоречить целевому ключу по общей оси.
Явная карта на верхнем уровне композиции переопределяет автоматический выбор:
{
"variantMigration": {
"rootId": "<existing-component-set-id>",
"retained": {
"state=default": "<retained-default-component-id>",
"state=hover": "<retained-hover-component-id>"
},
"retiredIds": ["<unused-component-id>"],
"consumers": { "<unused-component-id>": "<retained-default-component-id>" }
}
}
Включите в retained каждый целевой вариант, у которого есть совместимый существующий компонент; ключ без совместимого компонента можно опустить — он создаётся, а при повторном Apply с той же картой созданный компонент сохраняется по точному ключу. Все удаляемые компоненты перечислите в retiredIds; consumers необязателен и задаёт, на какой сохраняемый компонент переводятся экземпляры удаляемого варианта без совместимого назначения. Сохраняемый компонент не должен противоречить целевому ключу по общей оси; при полном переименовании осей карта — решение автора. Завершённую карту можно применять повторно. Generate не использует миграцию для отсутствующего корня.
Коллизии, экземпляры без назначения, нечитаемый статус публикации и неудавшаяся замена компонента останавливают операцию; до первой записи ничего не меняется. Идентификаторы сохраняемых набора, компонентов и дочерних слоёв остаются прежними. Если одна композиция использует удаляемые варианты другой, сначала мигрируйте потребителя.
Не редактируйте и не публикуйте компоненты параллельно с миграцией. Перед каждым удалением повторно проверяются публикация и экземпляры. Позднее изменение или ошибка записи останавливают операцию с перечнем оставшихся IDs и без отметки об успешном завершении; Figma не предоставляет атомарного отката уже выполненных записей.