Соберите дизайн-систему из ролей и токенов
Построение дизайн-системы с нуля или аудит существующей. Типографика, цвета, компоненты, сетка, spacing. Используйте при создании нового продукта или когда дизайн-система нуждается в систематизации.
Как агент работает
Сначала проверяется, окупается ли система вообще: она обязательна, если UI пишут трое и больше, экранов от тридцати, на одном бренде живут два продукта, нужна тёмная тема или white-label либо интерфейс отдаётся интеграторам как контракт. Дальше идёт измерение разнобоя в клоне через sandbox_bash, а не разговор о вкусах: до 40 уникальных цветов здорово, свыше 90 — разнобой; px-значений здорово до 15, тревожно 15-30; начертаний не больше трёх; радиусов и теней до четырёх; файлов с inline-стилями не больше 5%.
Тема переключается только через средний уровень токенов, потому что тема — переопределение ролей, а не значений: если в компоненте записано конкретное значение серого, для тёмной темы придётся решать для каждого места отдельно, стало оно фоном страницы, карточки или разделителем. В тёмной теме роль, указывающая на ступень N, указывает на 1000 минус N того же тона, насыщенность акцента снижается на 10-20%, фон страницы берётся 900-950 и никогда не чистый чёрный, дающий на OLED ореолы вокруг светлого текста. Имена описывают роль, а не вид.
Контраст — проверяемое требование, а не мнение: WCAG 2.1 и 2.2 уровня AA, с которыми гармонизирован российский ГОСТ Р 52872-2019, дают 4.5:1 для обычного текста, 3:1 для крупного от 24 px или 18.66 px полужирного и 3:1 для границ полей, иконок-действий и фокус-кольца. Проверка всегда находит одно и то же: приглушённый серый на цветной поверхности, белый текст на жёлтой или оранжевой кнопке-warning, почти невидимую границу поля около 1.5:1 и состояние, закодированное только цветом. Снятие outline без замены — дефект, а не стиль.
Кириллица ломает шкалы, собранные под латиницу: русский текст на 10-15% длиннее, а интерфейсные слова вдвое, поэтому кнопки проектируются от контента с min-width и проверяются на самой длинной реальной подписи. При line-height 1 браузер обрезает надстрочные знаки у «Й» и «Ё», минимум для заголовков — 1.15-1.25. Символ рубля выпадает из урезанных сабсетов, tabular-nums обязателен для колонок с суммами, а переносы работают только при lang=ru. Шкала отступов строится от 4 px, и обязательно проверяется ширина 360 px — макет, сделанный на 390, ломается именно там.
Навык не выдаёт картинки компонентов за документацию: изображение расходится с кодом в день правки, поэтому живая документация генерирует таблицу токенов из файла токенов и показывает матрицу состояний рядом с копируемым кодом. Правило, которое нельзя проверить линтером, будет нарушено, поэтому внедрение идёт фазами без остановки разработки: инвентаризация, токены поверх существующего без визуальных изменений, семантический слой, ядро компонентов по частоте использования, миграция по правилу касания и гард в CI на изменённых строках диффа.
Дизайн-система: построить с нуля или привести в порядок
1. Сначала посчитай, окупается ли
Когда обязательна
Хотя бы одно из: ≥ 3 человек пишут UI; ≥ 30 экранов; два продукта на одном бренде (кабинет, приложение, виджет в Битрикс24); нужна тёмная тема, вторая плотность или white-label; UI отдаётся интеграторам, где система работает как контракт.
2. Инвентаризация: посчитай реальный разнобой
Начинай с измерения — через sandbox_bash в клоне (git_clone).
Как читать числа
| Показатель | Здорово | Тревожно | Разнобой |
|---|---|---|---|
| Уникальных цветов | ≤ 40 | 40–90 | > 90 |
| Уникальных px-значений | ≤ 15 | 15–30 | > 30 |
| Начертаний шрифта | ≤ 3 | 4 | ≥ 5 |
| Радиусов / теней | ≤ 4 | 5–8 | ≥ 9 |
Файлов с inline style={{}} | ≤ 5% | 5–15% | > 15% |
3. Три уровня токенов
Почему без среднего уровня тема не переключается
Тема — переопределение ролей, а не значений. Если в компоненте написано bg-gray-50, для тёмной темы придётся найти все такие места и решить для каждого, чем оно становится: фоном страницы (gray-950), карточки (gray-900) или разделителем (gray-800). Одно исходное значение расходится на три — ручной проход по всему коду, ровно то, что система должна была отменить. С ролями тема — один блок:
4. Палитра, которую не придётся собирать второй раз
Тёмная тема как зеркало лестницы
Роль, указывающая в светлой теме на ступень N, в тёмной указывает на 1000 − N того же тона. Две поправки: насыщенность акцента (C) снижается на 10–20%, иначе он звенит на тёмном; фон страницы — 900–950, но никогда #000000, дающий на OLED ореолы вокруг светлого текста. Тени в тёмной теме почти не работают: иерархию поверхностей передавай светлотой фона и границей.
Состав: нейтраль и бренд по 11 ступеней, интенты success/warning/danger/info по 4–5 и отдельная палитра для графиков (6–8 цветов, различимых при дейтеранопии) — красный в графике значит категорию, а не ошибку.
5. Контраст: проверяемое требование, а не мнение
Минимумы (WCAG 2.1/2.2 уровня AA; ГОСТ Р 52872-2019 — российский стандарт доступности цифровых интерфейсов, гармонизированный с этими требованиями): обычный текст 4.5:1; крупный (≥ 24 px или ≥ 18.66 px полужирного) 3:1; границы полей, иконки-действия, индикаторы состояния и фокус-кольцо 3:1 к соседнему фону.
Провалы, которые она находит всегда
text-muted на цветной поверхности (серый рассчитан на белый и на bg-info-subtle даёт около 3:1); белый текст на кнопке-warning (жёлтый и оранжевый ступени 500 почти никогда не держат 4.5:1 с белым); граница поля gray-200 на белом (около 1.5:1, поля фактически невидимы); состояние, закодированное только цветом (красная рамка без иконки не читается при дальтонизме). Фокус-кольцо — один токен --color-focus-ring (3:1 к обеим поверхностям) и :focus-visible; снятие outline без замены — дефект, а не стиль.
6. Типографическая шкала: шаг и его обоснование
Для интерфейсов бери отношение 1.2, реже 1.25; 1.333 и 1.5 — для лендингов. Шаг 1.333 от 16 px даёт 16 → 21.3 → 28.4 → 37.9: промежуточные размеры, нужные в плотном интерфейсе (подпись поля, значение метрики, заголовок карточки), в шкалу не попадают и их добирают на глаз — так и появляются те самые 40 уникальных размеров из инвентаризации. Шаг 1.2 даёт 16 → 19.2 → 23 → 27.6 → 33.2: ступени различимы, набор укладывается в 7–8 значений.
| Токен | Размер | Line-height | Назначение |
|---|---|---|---|
text-xs | 12 | 16 | служебные метки, единицы измерения |
text-sm | 14 | 20 | подписи полей, плотные таблицы |
text-base | 16 | 24 | основной текст, поля ввода |
text-lg | 20 | 28 | заголовок карточки, метрика |
text-xl | 24 | 32 | заголовок раздела |
text-2xl | 30 | 38 | заголовок страницы |
Поля ввода на мобильных — не меньше 16 px: Safari на iOS зумит страницу при фокусе в поле меньшего кегля, и это выглядит как баг вёрстки. Начертаний не больше трёх (400/500/700), длина строки 60–75 знаков (max-width: 65ch).
7. Кириллица: что ломается в шкалах под латиницу
Русский текст на 10–15% длиннее английского, а интерфейсные слова — вдвое: Save → «Сохранить», Sign in → «Войти в систему». Кнопки фиксированной ширины ломаются, ярлыки вкладок обрезаются, шапки таблиц рвут строку. Проектируй кнопки от контента с min-width и держи в библиотеке пример с длинной русской подписью: компонент проверяется на самом длинном реальном ярлыке.
У кириллицы почти нет верхних выносных элементов, зато «Й» и «Ё» несут надстрочные знаки, и при line-height: 1 (то самое leading-none, которое советуют для крупных заголовков) браузер обрезает крышечку у «Й» и точки у «Ё». Минимум для заголовков — 1.15–1.25, не 1.0.
Безопасный выбор: Golos Text (Paratype по заказу «Смены», SIL OFL, свободен и для коммерции), PT Sans / PT Mono, Inter, Manrope, Onest. Фирменные шрифты экосистем и банков лицензированы под владельцев.
Числа, рубль, переносы, регистр
-
₽ (U+20BD) добавлен в Юникод недавно, в урезанных сабсетах его нет — выводится «тофу» (□). Не вырезай его при сабсеттинге; формат суммы —
1 250 ₽с неразрывным пробелом. -
font-variant-numeric: tabular-numsобязателен для колонок с суммами и процентами: без него разряды не выстраиваются и сравнить два числа глазом невозможно. Это токен системы, а не решение автора таблицы. -
hyphens: autoработает только сlang="ru": без него словарь переносов не подключится, и длинные слова порвут узкие колонки. Для артикулов —overflow-wrap: anywhere. -
uppercaseна кириллице читается хуже, чем на латинице: если нужен —letter-spacing: 0.04em. «ё» в подписях пишется, в поиске нормализуется, иначе «Королёв» не найдётся по «Королев».
8. Сетка, отступы и правило внутреннего и внешнего
База шкалы — 4 px: восьмипиксельная груба для деловых интерфейсов, между иконкой и текстом в строке таблицы нужно 4 или 6. Шкала: 0, 2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80; значений вне шкалы в коде быть не должно. Радиусов — четыре (none, sm 4–6, md 8–10, full), внешний радиус = внутренний + отступ между ними. Теней — не больше четырёх, каждая привязана к уровню поверхности, а не к настроению.
Правило внутреннего и внешнего: расстояние между элементами всегда меньше, чем расстояние вокруг группы — это единственный механизм, по которому глаз понимает, что к чему относится. Подпись отстоит от своего поля на 8 → поле от следующей пары на 16 → группа полей от секции на 32, соотношение соседних уровней не меньше 1.5×. Самый частый дефект: подпись прижата к предыдущему полю сильнее, чем к своему — форма читается со смещением на строку, и человек вводит данные не туда.
Сетка: десктоп 12 колонок, gutter 24, максимум 1200–1440; мобильный 4 колонки, gutter 16. Брейкпоинты 640 / 768 / 1024 / 1280 / 1536, и обязательно проверяй 360 px — ширину большой доли парка бюджетных Android у российской аудитории: макет, сделанный на 390, ломается на 360 в местах длинных русских подписей.
9. Состояния компонента: полный набор обязателен
Интерактивный элемент: default, hover, active, focus-visible, disabled, loading, selected, error, read-only. Контейнер данных: loading (скелетон по размеру реального контента), empty (с объяснением, как наполнить), no-results (с кнопкой сброса фильтра), error (причина + «повторить»), partial, overflow.
Что забывают, в порядке частоты
-
focus-visible— снеслиoutlineради вида, клавиатурная навигация умерла. -
no-resultsотдельно отempty— пользователь видит «У вас пока нет заказов» при активном фильтре и решает, что данные потерялись. -
loadingна самой кнопке — без блокировки повторного нажатия форма уходит дважды; в биллинге это двойное списание. -
disabledбез объяснения причины — кнопка серая, почему, неизвестно. -
Длинный контент — состояния проверены на коротком тексте, а переполнение строки не решено: обрезка, перенос или сжатие соседей должны быть выбраны, а не случиться.
-
Hover на тач-устройствах (залипает после тапа — оборачивай в
@media (hover: hover)) иprefers-reduced-motion, которым анимации обязаны выключаться целиком.
Комбинаций получается «варианты × размеры × состояния»: у кнопки с 4 вариантами, 3 размерами и 10 состояниями — 120. Их не рисуют, их генерируют циклом по спискам, чтобы новый вариант сам появился везде.
10. Именование
Формат: <категория>-<роль>-<модификатор>-<состояние>, например color-border-input-invalid. Имя описывает роль, а не вид: color-text-danger, а не color-text-red, потому что при смене красного на терракотовый имя red станет ложью, которую никто не пойдёт исправлять. Не кодируй значение в имени: space-16 привязывает имя к пикселям и запрещает менять шкалу. Модификаторы интенсивности зафиксируй заранее — subtle → default → emphasis → inverse, а пропсы делай по ролям (variant, size, tone): булевы под каждый вариант (isPrimary, isDanger) допускают невозможные сочетания. Имена вида primary2, blueNew, cardV2 означают, что нужной роли нет — её надо завести, а не наращивать суффикс.
11. Документация, которой пользуются
Признак мёртвой документации — картинки компонентов: картинка расходится с кодом в день правки, и через месяц ей не верят. Живая документация генерирует таблицу токенов из файла токенов, показывает каждый компонент живым и матрицей всех состояний рядом с копируемым кодом вставки, называет случаи, когда компонент НЕ применять (самая полезная и почти всегда отсутствующая секция), и записывает решения с причинами: не «радиус 8», а «радиус 8, потому что при 12 в плотных таблицах углы съедают строку» — через полгода причину не помнит никто.
Правило, которое нельзя проверить линтером, будет нарушено. Документ живёт рядом с файлом токенов (edit_file), а не в вики: оторванная от кода документация расходится за спринт.
12. Внедрение в живой проект без остановки разработки
- Фаза 0 (0.5 дня). Инвентаризация — раздел 2: таблица разнобоя и базовая метрика.
- Фаза 1 (1–2 дня). Токены поверх существующего — те самые 15–20 доминирующих значений: визуально не меняется ничего, появляется словарь, ноль риска и конфликтов с чужими ветками.
- Фаза 2 (2–3 дня). Семантический слой и тема — роли плюс переопределения для тёмной темы, даже если тема пока не включается.
- Фаза 3 (5–10 дней). Ядро компонентов по частоте использования: почти всегда Button → Input → Select → Modal → Table → Toast, и первые три покрывают половину вхождений.
- Фаза 4 (постоянно). Миграция по правилу касания — новый код только на системе, старый переписывается, когда его и так открыли по задаче.
- Фаза 5. Гард в CI — запрет новых hex вне токенов на изменённых строках диффа.
14. Шаблон DESIGN.md
Отдавай заполненным: раздел, где нечего написать, — нерешённый вопрос.
Похожие навыки
Попробуйте этот навык
Зарегистрируйтесь и используйте навык «Дизайн-консультация» бесплатно.