DarkRiDDeR12 мин

Маленькая дизайн-система: начать с контракта кнопки, а не с каталога компонентов

FrontendКачество

Три одинаковые на вид кнопки редко ломаются одновременно. Одна берёт синий цвет из локального файла, вторая не показывает фокус, третья на disabled меняет только opacity, а четвёртая вместо понятного имени имеет иконку. Симптом кажется косметическим, пока пользователь не попадает в другой сценарий. Цена — каждый новый экран получает ещё один почти такой же control, а исправление цвета начинает менять поведение там, где его не ожидали.

В мае 2022 года я бы не начинал с «универсальной дизайн-системы». Сначала нужен узкий контракт одной primary button: именованные токены, обязательные состояния, семантическое имя, роль и список реальных мест использования. Это продолжает предыдущую статью о доступном control: внешний вид и name/role/state нельзя держать в разных случайных ветках. Пример ниже — локальная fixture, не DOM, не CSS и не visual regression test; он проверяет только объявленные данные.

Выбрать границу: одна кнопка, а не вся библиотека

Минимальная система отвечает на вопрос «какой button contract должны разделять эти три места», а не «как описать любой интерфейс». В неё входят пять токенов: background, foreground, focus ring, radius и gap. В неё входят пять состояний: default, hover, focus-visible, disabled и loading. Не все состояния обязаны выглядеть одинаково в каждом продукте, но их отсутствие не должно быть случайностью. Если loading невозможен для действия без сети, это решение нужно записать в contract, а не скрыть в одном компоненте.

Минимальный контракт primary button
ЧастьСимптом без неёПроверяемый фактДействие
Named tokenдва экрана называют один синий разными hex-значениямиу каждого required value есть стабильное имявынести значение в button.primary.* и искать локальные дубли
State matrixfocus или loading появляется только после жалобыdefault, hover, focus-visible, disabled, loading названы до реализациидля отсутствующего state принять явное product decision
Semantic contractиконка выглядит как кнопка, но не имеет понятного действияесть name, role button и declared stateоставить native host, если custom behavior не нужен
Usage inventoryправка profile-save ломает оплатуперечислены известные usage с контекстом и состояниемменять один token малым diff и повторять проверку мест

Токен — имя решения, а не переменная ради переменной

CSS Custom Properties допускает author-defined properties с префиксом -- и подстановку через var(). Это полезный механизм, но он не создаёт за команду словарь design tokens. Имя button.primary.background в этой статье — соглашение пакета: оно говорит, что значение относится к роли primary button, а не ко всем синим пикселям проекта. Поэтому не стоит сразу делать brand.blue.500 единственным входом для компонента: у роли должна быть собственная граница, даже если сегодня она ссылается на тот же цвет.

Проверка проста: у каждой величины есть имя, владелец и место потребления. Если в pull request появляется #2457D6 рядом с кнопкой, сначала спросите, это новый token или обход существующего. Если ответ «временно», зафиксируйте срок и конкретный rollback. Не нужно объявлять каждую тень и каждый margin глобальным token. Глобальность оправдана только повторяемым contract; одиночная геометрия остаётся локальной, пока не появится второй подтверждённый use case.

Учебная fixture: проверить данные до сборки CSS

Fixture создаёт in-memory объект с пятью named tokens, матрицей required states, declared name/role/state и usage inventory из трёх кнопок. Ещё в ней есть visual payload: ширины 375 и 1280, набор states и список token names. Это вход для будущего snapshot-процесса, а не screenshot, diff или PASS реального инструмента. Граница записана в самом объекте: DOM, browser, CSS compilation, HTTP и visual regression не запускались.

node web/scripts/upgrade-2022-05.mjs --verify-fixture
// PASS fixture: 13/13 assertions

// Проверяется локальный contract: tokens, states, semantic fields, inventory,
// declared visual payload, invalid configuration и rollback. Это не UI test.

Такой тест ловит дешёвую ошибку раньше рендера: кто-то добавил usage без имени, убрал loading из состояния или стал использовать token, которого contract не объявляет. Он не ловит контраст на реальном фоне, порядок клавиатуры, cascade в существующем CSS или изменение пикселей на устройстве. Это разные проверки. Их полезно добавлять следующими, но нельзя дорисовывать их результат к локальному объекту числом score или словом «доступно».

Поток малого button contract: пять named tokens поступают в компонент primary button с пятью обязательными состояниями; затем usage inventory перечисляет профиль, оплату и диалог; справа visual payload объявляет viewports и states, но помечен как не являющийся screenshot или test result.
Схема отделяет источник значения, contract компонента и будущий вход visual-проверки. Между ними нет выдуманного production-результата.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. Найдите одну повторяющуюся кнопку, у которой расходятся color, focus или label. Не группируйте сразу все controls.
  2. Причина. Выпишите, где лежат literal values, состояния и semantic fields. Обычно они принадлежат разным локальным файлам без общего contract.
  3. Проверка. Соберите usage inventory: context, name, role, состояние, локальные overrides. Затем запустите fixture и убедитесь, что declared payload не называют результатом visual test.
  4. Действие. Внесите один named token и одну state matrix для primary button. Оставьте native button там, где не требуется другой host.
  5. Откат. Сохраните прежнее значение token до правки. Если один usage изменился неожиданно, верните только token и разберите его локальный override.
  6. Следующая проверка. После contract запустите отдельную реальную visual и a11y-проверку в согласованной среде. Её артефакт должен содержать версии и наблюдения.

Где маленький contract заканчивается

Этот подход не выбирает типографику бренда, не строит темизацию, не мигрирует legacy CSS и не заменяет дизайн-ревью. Он также не доказывает WCAG-conformance: WCAG содержит проверяемые критерии, но локальная fixture не наблюдает страницу. Числа, hex-значения и названия из примера — учебные проектные решения. В другом продукте focus ring может иметь другое имя и значение; важнее, чтобы его существование и ответственность были явными.

Следующий проверяемый шаг — выбрать три настоящих usage одной primary button, составить inventory до изменения и договориться о минимальном payload для внешнего visual review. Если один usage требует другого состояния или семантики, не расширяйте contract по умолчанию. Сначала зафиксируйте причину: это variant той же кнопки или другой control. Такой вопрос экономит больше времени, чем ранний каталог из двадцати компонентов.

Историческая граница мая 2022

Текст опирается на Candidate Recommendation Draft CSS Custom Properties от 11 ноября 2021 года и Candidate Recommendation Draft WAI-ARIA 1.2 от 8 декабря 2021 года — оба снимка доступны до мая 2022-го. WCAG 2.1 здесь приведён как стабильная Recommendation 2018 года. Эти документы описывают CSS-механизм и accessibility semantics, но не утверждают, что названия tokens, inventory или payload из fixture существовали в конкретной команде.

Проверяемые источники