Правила оформления компонентов: проверяемый список

Источник: ds-docs/component-contracts/rules/component-rules.md — собирается из component-rules.json

Версия каталога 1.0, снят 2026-09-09. Правила лежат рядом машинным списком — component-rules.json; этот документ из него собирается, вручную его не правят.

Каждое правило — признак, который видно в файле, а не пожелание. У каждого есть, где смотреть в выгрузке (dump) и как то же самое достаётся из Plugin API (figma), — по ним пишется плагин-проверка.

🔴 ошибкаНарушение договорённости, из-за которого компонент нельзя описать контрактом или перенести в код без потерь
🟡 долгОтступление, которое переживём, но оно копит долг: чинить при ближайшей правке компонента
⚪ наблюдениеНаблюдение: не ошибка, но повод посмотреть глазами

Свод

Именование41
Свойства50
Оси50
Версии42
Токены61
Текст42
Слои60
Платформа31
Структура40
Всего417

Именование

NAME-001 — Имя компонента — Pascal Case с пробелами

🟡 долг. «Аннотация Pascal Case, только с пробелом» — так компонент ищется в Assets и читается разработчиком

В выгрузкеmasters[*] — имя набора; ключ корневого узла дерева
В Plugin APInode.name у COMPONENT_SET и COMPONENT
Сообщение дизайнеруИмя «{name}» не в Pascal Case с пробелом
Что делатьПереименовать: «Calculate Elephant Weight», а не «calculate_elephant_weight»
Проверяется скриптомда

NAME-002 — В имени нет смешанных алфавитов

🔴 ошибка. Слово выглядит латинским, а буква в нём кириллическая: компонент не находится поиском, слаг и имя переменной ломаются. В базе таких имён четыре: «Сalendar Widget», «Datе Input», «Icon Сontainer Сolor», ось «Сurrent slider»

В выгрузкеимя набора, имя оси, значение оси, имя слоя
В Plugin APInode.name, componentPropertyDefinitions — ключи и variantOptions
Сообщение дизайнеруВ «{name}» смешаны алфавиты: {word}
Что делатьЗаменить кириллические буквы на латинские (или наоборот — как задумано)
Проверяется скриптомда

NAME-003 — В имени нет лишних пробелов

🟡 долг. Двойной пробел и пробел на конце попадают в имя мастера и в путь узла: контракт и сверка видят два разных имени там, где дизайнер видит одно

В выгрузкеключи деревьев, имена мастеров
В Plugin APInode.name
Сообщение дизайнеруЛишние пробелы в имени «{name}»
Что делатьУбрать двойные и краевые пробелы
Проверяется скриптомда

NAME-004 — Опечатки в известных словах

🟡 долг. Опечатка в имени оси или значения расходится с соседними наборами и попадает в контракт как есть

В выгрузкеимена осей и значений
В Plugin APIcomponentPropertyDefinitions: ключи и variantOptions
Сообщение дизайнеруОпечатка: «{value}» вместо «{right}»
Что делатьПереименовать свойство или значение
Проверяется скриптомда

Свойства

PROP-001 — Boolean-свойство названо «Show …»

🟡 долг. «Название свойства всегда должно иметь форму Show „Название слоя или компонента“»: иначе неясно, что именно гасится

В выгрузкеdefs: {имя: {type: BOOLEAN}}
В Plugin APIcomponentPropertyDefinitions[key].type === 'BOOLEAN'
Сообщение дизайнеруЛогическое свойство «{name}» названо не по правилу «Show …»
Что делатьПереименовать в «Show {название слоя}»
Проверяется скриптомда

PROP-002 — Instance swap назван «Swap … Container» и лежит во frame «… Container»

🟡 долг. «Компонент со свойством Instance swap должен быть помещён во frame [„Название“ Container]», а свойство названо «Swap „Название“ Container» — по этому имени видно точку подстановки

В выгрузкеdefs: {имя: {type: INSTANCE_SWAP}}; путь узла-цели
В Plugin APIcomponentPropertyDefinitions[key].type === 'INSTANCE_SWAP'; node.parent.name
Сообщение дизайнеруПодстановка «{name}» названа не по правилу «Swap … Container»
Что делатьЗавернуть во frame «{Название} Container» и назвать свойство «Swap {Название} Container»
Проверяется скриптомда

PROP-003 — Boolean вешается на контейнер, а не на компонент

🟡 долг. «Если есть необходимость скрыть слой через Boolean props, используйте это на frame [„Название“ Container], а не на компоненте»: иначе подстановка и видимость дерутся за один узел

В выгрузкеprops у инстанса + hidden
В Plugin APIcomponentPropertyReferences.visible на узле типа INSTANCE
Сообщение дизайнеруВидимость повешена на инстанс «{name}», а не на его контейнер
Что делатьПеренести Boolean на frame-контейнер
Проверяется скриптомда

PROP-004 — Текстовые свойства названы по словарю

⚪ наблюдение. Словарь имён текста задан правилами: Label, Text, Description, Value, Placeholder, Default Value, Filled Value, Helper text, Warning message, Error message Empty/Filled и соседние

В выгрузкеdefs: {имя: {type: TEXT}}
В Plugin APIcomponentPropertyDefinitions[key].type === 'TEXT'
Сообщение дизайнеруТекстовое свойство «{name}» вне словаря имён
Что делатьВзять имя из словаря либо предложить пополнение владельцу ДС
Проверяется скриптомда

PROP-005 — Свойства перечислены по расположению в компоненте, слева направо

⚪ наблюдение. Главное правило источника: «Properties должны выводиться по логике их расположения в компоненте (визуально), слева направо»

В выгрузкепорядок ключей defs против порядка путей в дереве
В Plugin APIObject.keys(componentPropertyDefinitions) против координат целевых узлов
Сообщение дизайнеруПорядок свойств не совпадает с их расположением в компоненте
Что делатьПересобрать порядок свойств
Проверяется скриптомнет — нужен плагин: координаты узлов, страницы макета, стопы градиента

Оси

AXIS-001 — Ось Size названа значениями Small / Medium / Large / Max

🟡 долг. «Значения должны быть small (sm), medium (md), large (lg), max»: разнобой в размерах ломает сопоставление компонентов

В выгрузкеимена мастеров: Size=…
В Plugin APIvariantGroupProperties['Size'].values
Сообщение дизайнеруЗначение размера «{value}» вне ряда Small / Medium / Large / Max
Что делатьПереименовать значения оси
Проверяется скриптомда

AXIS-002 — Ось State названа значениями из словаря состояний

🟡 долг. Словарь состояний задан правилами: Default, Hovered, Focused Empty/Filled, Pressed, Filled, Filled Confirmed, Read Only, Warning, Warning Focused, Error Empty/Filled, Disabled Empty/Filled, Skeleton

В выгрузкеимена мастеров: State=…
В Plugin APIvariantGroupProperties['State'].values
Сообщение дизайнеруСостояние «{value}» вне словаря состояний
Что делатьВзять значение из словаря либо предложить пополнение
Проверяется скриптомда

AXIS-003 — Ось с единственным значением не заводится

🟡 долг. Выбора нет, а ось есть: она множит имена мастеров и путает дизайнера

В выгрузкеоси мастеров
В Plugin APIvariantGroupProperties[key].values.length === 1
Сообщение дизайнеруОсь «{axis}» заведена с единственным значением «{value}»
Что делатьУбрать ось или добавить второе значение
Проверяется скриптомда

AXIS-004 — Количество не заводится осью

🟡 долг. Ось из чисел (2, 3, 4, 5…) делает по мастеру на каждое число: это должно быть свойством или слотом, а не решёткой

В выгрузкеоси мастеров
В Plugin APIvariantGroupProperties[key].values
Сообщение дизайнеруКоличество задано осью «{axis}» ({values})
Что делатьЗаменить на слот или Instance swap
Проверяется скриптомда

AXIS-005 — Решётка вариантов полна или объяснима

⚪ наблюдение. Если мастеров вдвое меньше, чем сочетаний осей, из макета не следует, какие сочетания живые: разработчик не знает, что собирать

В выгрузкечисло мастеров против произведения значений осей
В Plugin APIcomponentSet.children.length против variantGroupProperties
Сообщение дизайнеруРешётка неполная: {count} мастеров при {full} сочетаниях
Что делатьЛибо дособрать мастера, либо описать правило «какие сочетания живут»
Проверяется скриптомда

Версии

VER-001 — Отменённая версия помечена крестом в значении оси

⚪ наблюдение. Обе версии держат в одном наборе, чтобы продукт переходил сменой значения, а не свапом компонента. Отменённая ветка помечается «❌ Deprecated», живая — «🟢 Stable»

В выгрузкеоси мастеров
В Plugin APIvariantGroupProperties['Version'].values
Сообщение дизайнеруВерсия «{value}» помечена не по правилу «❌ Deprecated» / «🟢 Stable»
Что делатьПривести значения к «❌ Deprecated» и «🟢 Stable»
Проверяется скриптомда

VER-002 — Умолчание набора — живая версия

🔴 ошибка. Перетащив компонент в макет, дизайнер получает мастер по умолчанию. Если умолчание указывает на отменённую версию, старое расползается по макетам само

В выгрузкепервый ключ masters
В Plugin APIcomponentSet.defaultVariant.name
Сообщение дизайнеруУмолчание набора указывает на отменённую версию: {master}
Что делатьПереставить порядок мастеров так, чтобы первым шёл 🟢 Stable
Проверяется скриптомда

VER-003 — Отменённая ось или свойство не остаются в решётке

🟡 долг. Ось с крестом в имени («❌ Progress») стоит у каждого мастера и множит решётку, хотя выбирать в ней нечего

В выгрузкеимена осей, defs
В Plugin APIvariantGroupProperties и componentPropertyDefinitions — ключи
Сообщение дизайнеруОтменённая ось «{axis}» осталась в решётке
Что делатьУбрать ось вместе с отменёнными мастерами
Проверяется скриптомда

VER-004 — Живой компонент не собран на отменённом

🔴 ошибка. Внутри живого стоит инстанс отменённой версии ребёнка — переход на новую версию не доведён, и долг расходится дальше по макетам

В выгрузкеprops у инстанса; of
В Plugin APIinstance.componentProperties, instance.mainComponent.name
Сообщение дизайнеруВнутри стоит отменённое: {child} — {prop}
Что делатьПереключить версию на 🟢 Stable или заменить компонент на живой
Проверяется скриптомда

Токены

TOK-001 — Отступы заданы токенами space, а не числами

🟡 долг. По базе 85 % ненулевых промежутков и 97 % полей уже стоят токеном: числа остаются там, где отступ подогнан под картинку

В выгрузкеpad и L[3] числом вместо имени токена
В Plugin APInode.boundVariables.paddingLeft и соседние; itemSpacing
Сообщение дизайнеруОтступ {value} задан числом, а не токеном space
Что делатьПривязать к переменной space-*
Проверяется скриптомда

TOK-002 — Отступ лежит на шкале space

🟡 долг. Шкала space: 0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80. Значения вне её — подгонка, которую в коде не повторить

В выгрузкеpad, L[3]
В Plugin APIpaddingTop/Right/Bottom/Left, itemSpacing
Сообщение дизайнеруОтступ {value} вне шкалы space
Что делатьВзять ближайшую ступень шкалы
Проверяется скриптомда

TOK-003 — Промежуток авто-слоя не оставлен умолчанием Figma

⚪ наблюдение. Новый авто-слой получает промежуток 10. В базе 99 таких мест: отступ никто не выбирал, он достался по умолчанию

В выгрузкеL[3] === 10 без токена
В Plugin APInode.itemSpacing === 10 && !node.boundVariables.itemSpacing
Сообщение дизайнеруПромежуток 10 — умолчание Figma, а не выбор
Что делатьЗадать токеном либо поставить 0
Проверяется скриптомда

TOK-004 — Скругление лежит на шкале radius

🟡 долг. Шкала radius: 0, 4, 6, 8, 12, 16, 20, 24, 32, 40, 999 плюс четыре особых. В базе нашлось 100 — вне шкалы

В выгрузкеr
В Plugin APIcornerRadius, topLeftRadius и соседние
Сообщение дизайнеруСкругление {value} вне шкалы radius
Что делатьВзять ступень шкалы; «полное скругление» — 999
Проверяется скриптомда

TOK-005 — Скругление привязано к переменной

⚪ наблюдение. Из 3118 углов базы не привязан ни один: числа совпадают со шкалой, но связи с токеном нет — при смене шкалы макет не поедет за ней

В выгрузкеr числом
В Plugin APInode.boundVariables.topLeftRadius и соседние
Сообщение дизайнеруСкругление {value} задано числом, а не токеном radius
Что делатьПрименить переменную radius-*
Проверяется скриптомда

TOK-006 — Цвет задан токеном, а не литералом

🔴 ошибка. Литеральный цвет не переключится в тёмную тему и не переедет в код. Исключение — логотипы и графика: у них токенов нет и быть не должно, цвет там часть рисунка. Признак исключения — обойма «Icon Container Color» либо слово-приставка в имени: «Logo /», «Bank Logo /», «Icon /», «Vector»

В выгрузкеf вида rgb(…)
В Plugin APIfills[0].boundVariables.color; признак графики — имя главного компонента предка-инстанса и имена узлов пути
Сообщение дизайнеруЦвет {value} задан литералом
Что делатьПривязать к переменной цвета
Проверяется скриптомда

Текст

TXT-001 — У надписи назначен текстовый стиль

🔴 ошибка. Без стиля начертание держится на переменных напрямую: контракт не может сослаться на стиль, а разработчик — понять, что это за текст

В выгрузкеt === TEXT без st
В Plugin APInode.textStyleId === ''
Сообщение дизайнеруУ надписи «{name}» нет текстового стиля
Что делатьНазначить стиль из библиотеки
Проверяется скриптомда

TXT-002 — Переменная типографики не переопределена поверх стиля

🟡 долг. Текстовый стиль Astra DS сам собран из переменных (font-family, font-size, line-height, letter-spacing, font-weight) — переменные на узле это норма. Дефект другой: на узле стоит своя переменная, отличная от стиля. Тогда стиль перенастроят, а надпись за ним не пойдёт. Правило работает только там, где стиль читается: в файле дизайн-системы стили локальные и видны, в продуктовом файле они приходят библиотекой и своих привязок не отдают — там правило молчит

В выгрузкене виден: в выгрузке нет привязок самого стиля — правило проверяет только плагин
В Plugin APIaliasIds(node.boundVariables[key]) против aliasIds(style.boundVariables[key]); привязки у текста посегментные — это массивы
Сообщение дизайнеруУ надписи «{name}» переменная поверх стиля: {fields}
Что делатьСнять переменную с узла — пусть значение приходит из стиля; либо завести отдельный стиль
Проверяется скриптомнет — нужен плагин: координаты узлов, страницы макета, стопы градиента

TXT-003 — Цвет надписи не литеральный чёрный

🔴 ошибка. Литеральный чёрный — след обёртки «Text / …», где видимый цвет даёт прямоугольник ⚒︎ Text Color поверх надписи

В выгрузкеt === TEXT, f === rgb(0,0,0)
В Plugin APIfills[0].color при пустом boundVariables
Сообщение дизайнеруЦвет надписи «{name}» — литеральный чёрный
Что делатьПривязать цвет к переменной текста
Проверяется скриптомда

TXT-004 — Обрезка хвоста задана осями обёртки, а не руками

⚪ наблюдение. У обёртки «Text / …» есть оси Max Lines и Text Overflow: число строк и способ обрезки читаются оттуда. Полоска ⚒︎ Text Overflow Blur, положенная руками, — приём макета

В выгрузкепуть узла содержит ⚒︎ Text Overflow Blur
В Plugin APIinstance.mainComponent.name.startsWith('⚒︎ Text Overflow Blur')
Сообщение дизайнеруПолоска размытия положена руками рядом с надписью
Что делатьЛибо взять обёртку с Text Overflow=Blur, либо оставить и пометить в описании
Проверяется скриптомда

Слои

LAY-001 — Подкраска состояния не лежит поверх содержимого

🟡 долг. У слоя состояния два вида: обойма, внутри которой лежит содержимое, и служебный инстанс ⚒︎ State Layer, который кладут абсолютно поверх — так показывают нажатие, не плодя вариантов. Ни к тому, ни к другому порядок в стопке не относится. Правило остаётся для третьего случая: пустая обойма-подкраска, положенная последней, закроет собой содержимое

В выгрузкепуть узла, наличие детей, abs, порядок в родителе
В Plugin APInode.children.length, node.layoutPositioning, индекс в parent.children
Сообщение дизайнеруПустая обойма состояния «{name}» лежит поверх содержимого
Что делатьПереставить под содержимое либо вложить содержимое внутрь обоймы
Проверяется скриптомда

LAY-002 — Слой состояния красится токеном своего состояния

🟡 долг. У слоя состояния своя таблица: наведение — hover-bg-dark (на тёмном -light), нажатие — press-bg-dark, фокус — focus-bg. Аудит базы нашёл три отклонения: Numkey красит фокус наведением, Tab Item — нажатие фокусом, Icon Button привязан к переменным без имени

В выгрузкеf у узла State layer в мастере с нужным значением State
В Plugin APIfills[0].boundVariables.color → имя переменной через getVariableByIdAsync
Сообщение дизайнеруВ состоянии {state} слой красится «{token}», а не {expected}
Что делатьПоставить токен состояния по таблице фундамента
Проверяется скриптомда

LAY-003 — У обоймы состояния есть ось State

🟡 долг. Обойма «State layer …» красится по мастеру: без оси State у компонента состояний попросту нет. Исключение — служебный инстанс ⚒︎ State Layer: он несёт состояние своим свойством, ради того и заведён, чтобы не плодить варианты. Там оси у компонента может и не быть, и это норма

В выгрузкеналичие State layer / 🥁 при отсутствии оси State
В Plugin APIпоиск узла State layer и variantGroupProperties['State']
Сообщение дизайнеруОбойма состояния есть, а оси State нет
Что делатьЗавести ось State со значениями Default, Hovered, Pressed
Проверяется скриптомда

LAY-004 — Скрытые слои не копятся в дереве

⚪ наблюдение. Слой лежит в дереве и гасится видимостью: события он ловить не перестаёт, а в код уезжает лишним узлом

В выгрузкеhidden === true
В Plugin APInode.visible === false && !componentPropertyReferences.visible
Сообщение дизайнеруСкрытый слой «{name}» без свойства видимости
Что делатьУдалить слой или завести Boolean «Show …»
Проверяется скриптомда

LAY-005 — Раскладка — авто-слой, а не сетка и не абсолют

⚪ наблюдение. Сетка (grid) и абсолютные слои в вёрстке разворачиваются иначе: контракт их пишет, но переносить их дороже

В выгрузкеL[0] === 'GRID', abs === true
В Plugin APInode.layoutMode, node.layoutPositioning
Сообщение дизайнеруРаскладка сеткой или абсолютное положение у «{name}»
Что делатьПереложить на авто-слой, если это не намеренное покрытие
Проверяется скриптомда

LAY-006 — Высота компонента — счётное число

⚪ наблюдение. «Хорошим тоном считается делать высоту компонентов счётным числом» — фиксировать токенами размера или min/max

В выгрузкеwh[1]
В Plugin APInode.height
Сообщение дизайнеруВысота {value} не кратна 4
Что делатьПодогнать размеры слоёв или зафиксировать min/max
Проверяется скриптомда

Платформа

PLT-001 — Размер экрана не прибивается числом

🔴 ошибка. Размер устройства подставляет локальная коллекция режимов (Apple, Android, Bottom Nav Bar, Web, Internet Bank View, WebView). Это вариация показа, а не размер компонента: прибитые minHeight и maxHeight переносить в код нельзя

В выгрузкеwh[1] из ряда высот экрана, minHeight === maxHeight === высоте
В Plugin APInode.height, node.minHeight, node.maxHeight
Сообщение дизайнеруВысота экрана {value} прибита min/max
Что делатьСнять пределы: размер задаёт режим коллекции платформы
Проверяется скриптомда

PLT-002 — SYSTEM-слой — место платформы, а не компонент

⚪ наблюдение. Слои SYSTEM Status Bar, SYSTEM Bottom Nav Bar, SYSTEM Keyboard и соседние рисует операционная система: их не документируют и не верстают

В выгрузкеимя узла
В Plugin APIinstance.mainComponent.name.startsWith('SYSTEM')
Сообщение дизайнеруSYSTEM-слой «{name}» внутри компонента
Что делатьОставить как есть, но не переносить в код: это место под элемент платформы
Проверяется скриптомда

PLT-003 — Вставка безопасной зоны — отступ, а не слой

⚪ наблюдение. injected-safe-area-inset-top и -bottom в React присылает host-приложение переменной, во Flutter отступ проставляет платформа. Числа 50, 32, 48 — рост одного устройства

В выгрузкеимя узла
В Plugin APIinstance.mainComponent.name.startsWith('injected-safe-area')
Сообщение дизайнеруВставка безопасной зоны «{name}» стоит слоем
Что делатьОставить в макете, но в код переносить отступом
Проверяется скриптомда

Структура

STR-002 — Эффект берётся стилем, а не выставляется руками

🟡 долг. Тень и размытие живут стилями эффектов: заданные руками, они расходятся между компонентами. Исключение — скелетон: мерцание загрузки собирают вручную, стиля под него в файле нет, и правило его не трогает

В выгрузкеfx === 'none' при наличии эффекта
В Plugin APInode.effects.length && node.effectStyleId === ''
Сообщение дизайнеруЭффект у «{name}» выставлен вручную
Что делатьПрименить стиль эффекта
Проверяется скриптомда

STR-003 — Градиент собран на переменных

⚪ наблюдение. У полоски размытия каждый стоп привязан к переменной (layer-0N-blur-gradient-0M). Градиент из литералов в тёмной теме не переключится

В выгрузкеf === 'GRADIENT_LINEAR' (стопы в выгрузку не попадают — проверять в плагине)
В Plugin APIfills[0].gradientStops[*].boundVariables.color
Сообщение дизайнеруГрадиент у «{name}» собран из литеральных цветов
Что делатьПривязать стопы к переменным
Проверяется скриптомда

STR-004 — Компонент не тянет чужую библиотеку без нужды

⚪ наблюдение. Значки приходят из подключённой библиотеки «Astra DS: Icons»: их переменные не входят в наши коллекции, и цвет им задаёт обойма

В выгрузкеинстанс, чьё имя (of) есть в tools/figma-lint/library-names.json — компонент чужой библиотеки, которого нет у нас, — с литералом rgb в переопределении ov или в заливке f. Фирменная графика (Logo, Bank Logo, Brand) не в счёт. Хеш в VariableID:<хеш>/<id> — признак не нарушения, а нормы: значок покрашен переменной обоймы
В Plugin APIinstance.mainComponent.remote === true
Сообщение дизайнеруКомпонент из чужой библиотеки красится сам
Что делатьКрасить обоймой icon-container
Проверяется скриптомда

SRC-001 — Компонент пришёл из известной библиотеки

⚪ наблюдение. Внутри компонента дизайн-системы должны стоять компоненты той же системы или подключённой библиотеки значков. Чужой компонент — след копирования из продуктового файла: он не обновится вместе с системой

В выгрузкене виден: в выгрузке ключей компонентов нет — правило работает только в плагине
В Plugin APImainComponent.remote и mainComponent.key против tools/figma-lint/library-registry.json
Сообщение дизайнеруКомпонент «{name}» не из наших библиотек
Что делатьЗаменить на компонент Astra DS либо принести компонент в систему
Проверяется скриптомнет — нужен плагин: координаты узлов, страницы макета, стопы градиента

Отменённые правила

Правила, которые пробовали и сняли: признак оказался нормальной практикой, а не дефектом. Оставлены здесь, чтобы их не завели заново.

ПравилоБылоПочему снято
STR-001Вложен набор, а не отдельный мастерВкладывать в компонент отдельный мастер — нормальная практика Astra DS, а не дефект: так берут нужный вид детали, и свойства набора у инстанса при этом остаются. Правило давало 110 срабатываний по базе, все ложные

Откуда правила

  • ../reference/guidelines/component-authoring-rules.md — «Правила оформления компонентов», версия 2.0 (buildin.ai)
  • ../reference/guidelines/naming-tags.md — теги-префиксы и суффиксы
  • foundations/*.md — фундаменты: state-layer, system-chrome, platform, versions, text-wrapper, space, radius
  • skills/astra-component-contract/scripts/contract.mjs — валидатор контрактов: те же правила в коде

Как проверить

node rules/lint-dump.mjs dumps/badge.json
node rules/lint-dump.mjs --dir dumps --summary

Линтер применяет к выгрузке те правила, у которых auto: true. Остальные ждут плагина.