Tokens

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Оси вариантов и взаимоисключающие состояния
СтилизацияstylesLayout, оформление и переопределения по вариантам
Свойства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 в основном компоненте.

JSON
{
  "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:

JSON
{
  "$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:

JSON
{
  "$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-инстансу в INSTANCE refs и 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

  1. Вы пишете JSON вручную или бутстрапите его через Get Code.
  2. SXL Studio парсит и валидирует файл.
  3. Generate создаёт новый компонент/набор, Apply обновляет отслеживаемые ноды.
  4. Трекинг хранится в 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(), который заводит и свойство слота). Внутри набора вариантов слой остаётся контейнером на своём месте (идентификатор сохраняется): набор получает новое свойство слота, и проход привязки слотов связывает контейнер с ним. Обратная смена SLOTFRAME пересобирает слой как обычный фрейм и удаляет слот. Уже связанные заглушки и слои, для которых свойство слота с тем же именем существует, не пересоздаются.

Свойство 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:

JSON
{
  "$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"Маркер типа файла
namestringИмя компонента / набора
structureнодаДерево слоёв
stylesобъектБазовые стили + правила селекторов
propsобъектОси вариантов
statesstring[]Взаимоисключающие оси, взятые из props
componentPropertiesобъектНативные свойства Figma
transitionsобъектПрототип-переходы (transition в ед. ч. принимается как легаси-алиас)
themeBindingобъектОсь варианта → режимы коллекции переменных
componentbooleanПо умолчанию true; false строит plain-ноды и запрещает непустые props
$descriptionstringОписание Figma на корневом компоненте/наборе
$descriptionMarkdownstringФорматированное описание Figma на корневом компоненте/наборе
documentationLinks{ uri: string }[]Ноль или одна корневая ссылка; чистый URL или одна Markdown-ссылка
$metadataanyНепрозрачные метаданные для тулинга; на генерацию не влияют
size / styleобъектОпциональные встроенные token-блоки (см. Advanced-поля)
selectorsautoautoГенерируется парсером из ключей styles — вручную не писать

Внимание Удалённые легаси-ключи дают ошибку парсинга: adapters, sizeStyles, colorStyles. А component: false несовместим с непустыми props.

Корневые описания и ссылка на документацию

$descriptionMarkdown хранит форматированное описание компонента и может содержать обычный Markdown. documentationLinks — отдельное поле Link в Figma, которое поддерживает не более одного элемента. В uri можно указать чистый абсолютный HTTP(S) URL или одну Markdown-ссылку, занимающую всю строку:

JSON
{
  "$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Вложенные дочерние нодыВложенные слоидочерние элементы
vectorPathsSVG-подобные path-данные (только VECTOR)Vector paths<path d="…">
vectorNetworkРедактируемая геометрия вершин (VECTOR)Vector network
viewBoxКоординатный бокс [x, y, w, h] (VECTOR)SVG viewBox
JSON
"structure": {
  "tag": "FRAME",
  "class": "root",
  "name": "Root",
  "children": []
}

JSON-пример на каждое поле:

JSON
{ "tag": "FRAME", "class": "card" }
JSON
{ "tag": "TEXT", "class": "label", "name": "Label", "content": "Continue" }
JSON
{
  "tag": "INSTANCE",
  "class": "icon",
  "ref": { "component": "circle-info", "properties": { "size": "md" } }
}
JSON
{
  "tag": "SLOT",
  "class": "content-slot",
  "slot": { "default": "WButton", "preferred": ["WButton", "WChip"] }
}
JSON
{
  "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Инстанс из refInstanceиспользование <Component />нужен ref; плейсхолдер, если не резолвится
ICONИконка-инстанс, резолвится из refIcon instance<Icon />как INSTANCE; color перекрашивает глиф
SLOTНативный слот (или fallback)Slot<slot> / {children}reference или children mode, не оба
RECTANGLEПрямоугольникRectangleблочный <div>заливка через fill
ELLIPSEЭллипсEllipseborder-radius: 50% div
LINEЛинияLine<hr> / разделитель
VECTORКастомный вектор из path / vector networkVector (Pen)инлайновый <svg><path>не импорт «сырого» SVG

JSON-пример на каждый тег:

JSON
{ "tag": "FRAME", "class": "card", "children": [] }
JSON
{ "tag": "TEXT", "class": "label", "content": "Continue" }
JSON
{ "tag": "COMPONENT", "class": "chip" }
JSON
{
  "tag": "INSTANCE",
  "class": "cta",
  "ref": { "component": "WButton", "properties": { "variant": "primary" } }
}
JSON
{
  "tag": "ICON",
  "class": "leading-icon",
  "ref": { "component": "circle-info" }
}
JSON
{ "tag": "SLOT", "class": "content-slot", "slot": { "default": "WButton" } }
JSON
{ "tag": "RECTANGLE", "class": "bg" }
JSON
{ "tag": "ELLIPSE", "class": "avatar-mask" }
JSON
{ "tag": "LINE", "class": "divider" }
JSON
{
  "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, поэтому текст и иконки ведут себя как обычно.

JSON
{
  "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" }]
    }
  ]
}
JSON
".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: []; отсутствие обеих форм недопустимо.

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

В разрабатываемой версии явное children: [] задаёт пустой слот; отсутствие и массива, и ссылки по-прежнему недопустимо.

Ограничения: у SLOT не может быть одновременно slot и непустых children; SLOT.ref валиден только в reference mode. SLOT ровно с одним дочерним INSTANCEref, без 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.

JSON
{
  "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}"
    }
  }
}
JSON
"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 без содержимого и настроенной ссылки недопустим.

JSON
{
  "$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:

JSON
"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 выбирает инстанс.

JSON
"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 меняет иконку внутри него:

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

ref.slots — переопределяет слоты внутри referenced-инстанса (op: replace, append, patch):

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

Вариант набора может оборачивать другой существующий компонент, переопределяя ref из вариантного селектора, — тогда одна нода INSTANCE обслуживает весь wrapper-набор:

JSON
"styles": {
  "$item=neutral-secondary-sm .item": {
    "ref": { "component": "WButton", "properties": { "style": "neutral", "variant": "secondary", "size": "sm" } }
  }
}

Props

КлючТипЧто делает
propsRecord<string, (string | boolean | number)[]>Объявляет каждую ось вариантов и её допустимые значения
statesstring[]Имена осей из props, которые являются взаимоисключающими состояниями

props объявляет все допустимые значения для каждой оси; каждая комбинация становится одним сгенерированным вариантом.

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

Поведение: при сопоставлении селекторов значения сравниваются как строки; ось с default использует его как fallback для отсутствующих правил, иначе fallback — первое значение. Держите число осей осознанным — комбинации растут мультипликативно.

states (взаимоисключающие оси)

states перечисляет оси из props, которые ведут себя как взаимоисключающие UI-состояния (:disabled, :loading), а не как комбинируемые варианты. Они исключаются из декартова произведения — каждое не-дефолтное значение добавляет один вариант поверх дефолтов.

JSON
"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 / :focus CSS потребителя); не-_ оси вроде loading / disabled эмитятся как реальные пропсы.

Расширенная объектная форма с combineWith (скрещивание состояния со структурными осями) зарезервирована, но пока не реализована — её использование вызывает ошибку валидации.


Component Properties

componentProperties задаёт нативные свойства Figma, доступные на инстансах.

ТипНа что указывает layerdefaultValue
TEXTclass в structureОбязателен; авто-выводится из content, если опущен
BOOLEANclass в structureОбязателен; по умолчанию true, если опущен
INSTANCE_SWAPclass в structureОбязателен
SLOTclass в structureНеобязательное legacy-поле; default-содержимое берётся из структуры
NESTED_INSTANCEИмя слоя вложенного инстанса (не class)Не обязателен

Пример на каждый тип:

JSON
"title": { "type": "TEXT", "layer": "title", "defaultValue": "Promo title" }
JSON
"showMeta": { "type": "BOOLEAN", "layer": "meta", "defaultValue": true }
JSON
"icon": { "type": "INSTANCE_SWAP", "layer": "icon", "defaultValue": "circle-info", "preferred": ["circle-info", "star"] }
JSON
"media": { "type": "SLOT", "layer": "media-slot", "defaultValue": "WImageTile" }
JSON
"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-layoutdirection, justifyContent, alignItems, gap, padding, …
Размер слояwidthType, width, min/maxWidth, aspectRatio, …
Позиционированиеposition, top/right/bottom/left, x/y, alignSelf, …
Gridrows/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
JSON
"styles": {
  ".root": { "direction": "row", "gap": 8, "padding": "12 16" },
  "$state=hover .root": { "background": "{color.brand.hover}" }
}

Селекторы и каскад

  • сначала применяются базовые стили; затем совпавшие селекторы по специфичности (больше условий — выше приоритет);
  • одинаковая специфичность → побеждает более позднее объявление в JSON;
  • descendant-селекторы резолвятся через dot-пути (.footer .itemfooter.item);
  • когда класс повторяется в разных ветках, предпочитайте полный путь (.header .item) вместо голого .item;
  • ключ селектора может содержать условия и классы в любом порядке ("$state=hover .root" и ".root $state=hover" равны), а вложенные блоки внутри него накапливают и условия, и классы: "$variant=secondary .root": { "background": "…", ".label": { "color": "…" } } даёт правила $variant=secondary .root и $variant=secondary .root .label.
JSON
"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"
canvasStackingZ-порядок сиблингов — first-on-top, last-on-top"canvasStacking": "first-on-top"
JSON
".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.

JSON
".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 / leftAbsolute-инсеты (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"
JSON
".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"
gridAutoRowsFigma сама добавляет и убирает строки вслед за детьми — 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" на фрейм внутри слота.

JSON
".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}"
colorCSS-подобный currentColor (текст + краска иконки) — hex или токен, "none""color": "{text.primary}"

color — это CSS-подобный currentColor: на TEXT задаёт заливку текста; на ICON / иконках-инстансах перекрашивает глиф (fill-иконки получают заливку, stroke-иконки — обводку). Ссылки на токены сохраняют привязку к переменной; литеральные цвета её заменяют. fill и strokes — низкоуровневые: используйте их для заливок фигур или прямой обводки. Для stroke-only иконки "fill": "none" оставляет только краску обводки.

Градиентный фон:

JSON
".hero": { "background": "linear-gradient(135deg, #7B5CFA 0%, #FA5CB4 100%)" }

Многослойный фон (изображение + оверлей):

JSON
".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)"
  ]
}

Границы, обводка, скругления

СвойствоЧто делает — значенияПример
borderShorthand "<толщина> <стиль> <цвет>"; "none" убирает обводку"border": "1px solid #E7EAF1"
borderColorЦвет обводки"borderColor": "#E7EAF1"
borderWidthТолщина обводки (px)"borderWidth": 1
borderStyleСтиль обводки — solid, dashed"borderStyle": "dashed"
borderRadiusРадиус скругления (px)"borderRadius": 20
cornerSmoothСглаживание углов 01 (squircle)"cornerSmooth": 0.6
strokeAlignПоложение обводки — inside, center, outside"strokeAlign": "inside"
strokeCap / strokeJoinСтиль конца / соединения линии"strokeCap": "ROUND"
strokeTopWeightstrokeLeftWeightТолщина обводки по стороне (px)"strokeTopWeight": 2
dashPatternПунктир — массив длин штрих/пропуск"dashPattern": [4, 4]
outline / outlineOffsetВнешняя обводка + отступ"outline": "2px solid #0D6EFD"
vectorCornerRadiiРадиусы по вершинам VECTOR (в порядке вершин)"vectorCornerRadii": [0, 8, 8, 0]
JSON
".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Прозрачность слоя 01, или "none" (= 1)"opacity": 0.56
blendModeРежим наложения слоя: normal, multiply, screen, overlay, darken, lighten, …"blendMode": "multiply"
glassЭффект стекла, или "none" для сброса"glass": "none"
effectsПолный композит эффектов (алиас токена или слои)"effects": "{shadow.md}"
JSON
".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"
JSON
".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 не задан.
JSON
"$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
JSON
".action-main": { "component": "WButton", "instanceProperties": { "variant": "primary", "size": "md", "label": "Continue" } }
JSON
".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"
JSON
".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]:

JSON
"transitions": { "$state=default -> $state=hover": "on-hover smart-animate 200ms ease-out" }

Объектная форма с условием (именованный ключ, направление задают from и to):

JSON
"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"]
JSON
"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 не влияют.

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

Полный пример

Полный набор компонентов, затрагивающий большинство возможностей: многоосевые props, componentProperties, SLOT и INSTANCE, переопределения по селекторам, корневые transitions и themeBinding.

JSON
{
  "$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

  1. Задайте $type: "composition" и стабильный name.
  2. Стройте props только из реальных осей вариантов; добавляйте states для взаимоисключающих условий.
  3. Для каждой ноды пишите валидные tag и class; добавляйте стабильный name для вложенно-адресуемых нод.
  4. Для инстансов пишите ref.component и только валидные примитивные ref.properties.
  5. Для слотов используйте reference mode (slot) или children mode (children), никогда оба.
  6. Все визуальные/layout-правила кладите в styles (база + переопределения по селекторам).
  7. Используйте корневые transitions и themeBinding, а не хаки на уровне слоя.
  8. Проверьте, что referenced-компоненты/токены существуют.

B) Composition JSON → React / Vue

  1. Читайте structure как дерево элементов; используйте class/path-ключи для резолва стилей.
  2. Резолвьте вариантные селекторы ($prop=value .class) против входящих пропсов.
  3. Уважайте видимость веток (display: "none").
  4. Трактуйте ref.component → импорт компонента, ref.properties → передаваемые пропсы.
  5. Трактуйте componentProperties, themeBinding, slotHostPipeline и $figma как метаданные дизайн-тайма, если рантайм их не поддерживает.
  6. Сохраняйте ссылки на токены ({...}) или пре-резолвьте их через свой token-движок.

C) Pre-flight валидация

  • structure существует и у каждой ноды есть tag + class;
  • styles существует и содержит только объектные значения;
  • themeBinding.prop существует в props;
  • нет удалённых легаси-ключей (adapters, sizeStyles, colorStyles);
  • нет конфликта режимов SLOT (slot с непустыми children).

Авторский ключ → runtime-ключ Figma (справка)

Авторский ключRuntime-ключ
directionlayoutMode
justifyContentprimaryAxisAlignItems
alignItemscounterAxisAlignItems
alignContentcounterAxisAlignContent
flexWraplayoutWrap
gap / wrapGapitemSpacing / counterAxisSpacing
widthType / heightTypelayoutSizingHorizontal / Vertical
alignSelf / flexGrowlayoutAlign / layoutGrow
positionlayoutPositioning
top / right / bottom / leftinsets → layout/position
rows / columnsgridRowCount / gridColumnCount
rowGap / columnGapgridRowGap / gridColumnGap
gridAutoFlow / gridAutoRowsgridItemsPositioning / gridAutoTracks
gridRow / gridColumnячейка сетки (счёт с 1 → с 0)
borderWidth / borderAlignstrokeWeight / strokeAlign
borderRadius / cornerSmoothcornerRadius / cornerSmoothing
textSizingtextAutoResize
textAlign / verticalAligntextAlignHorizontal / Vertical
rotaterotation
boxSizing / canvasStackingstrokesIncludedInLayout / itemReverseZIndex
justifySelf / gridAlignSelfgridChildHorizontalAlign / VerticalAlign

Авторское значение → enum Figma (справка)

СвойствоАвтор → enum
directionrow/column/none/gridHORIZONTAL/VERTICAL/NONE/GRID
justifyContentstart/center/end/space-between/space-evenly/space-aroundMIN/CENTER/MAX/SPACE_BETWEEN/SPACE_EVENLY/SPACE_AROUND
alignItemsstart/center/end/baselineMIN/CENTER/MAX/BASELINE
widthType / heightTypefixed/hug/fill (алиасы: auto→HUG, stretch→FILL) → FIXED/HUG/FILL
positionrelative/absolute/noneAUTO/ABSOLUTE/AUTO
textSizingfixed/height-auto/auto/truncateNONE/HEIGHT/WIDTH_AND_HEIGHT; truncate = NONE + textTruncation: ENDING
textCasenone/uppercase/lowercase/capitalize/small-capsORIGINAL/UPPER/LOWER/TITLE/SMALL_CAPS
strokeAligninside/center/outsideINSIDE/CENTER/OUTSIDE
maskalpha/vector/luminance/noneALPHA/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, а композиция собирается в файле компонентов. Внешняя переменная импортируется по опубликованному ключу и проверяется по возвращённому ключу и типу; библиотека должна быть опубликована и доступна. Один и тот же путь токена, экспортированный в несколько файлов, даёт диагностику о конфликте идентичностей.

JSON
{
  "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, ограничений размеров и ручного содержимого продолжают действовать отдельные правила владения.

Чтобы снять старый явный режим импортированной коллекции, укажите её точно:

JSON
{
  "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 не видит их экземпляры в других файлах, такие экземпляры продолжают отображаться, но теряют связь с библиотекой после публикации удаления. Если общих осей нет, требуется явная карта: ошибка содержит готовый позиционный вариант, который можно вставить и отредактировать; сохраняемый компонент не должен противоречить целевому ключу по общей оси.

Явная карта на верхнем уровне композиции переопределяет автоматический выбор:

JSON
{
  "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 не предоставляет атомарного отката уже выполненных записей.