{ "index": 202, "slug": "editorial-2022-05-field-design-system", "title": "Как менять токен дизайн-системы и не сломать соседний экран", "excerpt": "Практический разбор правки токена: как зафиксировать симптом, оценить радиус изменения, проверить состояния кнопки и оставить точный путь к откату.", "contentHtml": "
Цвет primary-кнопки меняют в одной строке, а ошибка появляется на другом экране. На форме оплаты пропадает focus ring. В диалоге отмены кнопка получает цвет действия подтверждения. В третьем месте новый токен не применяется, потому что компонент ждёт другое имя. Визуально это похоже на одну проблему. Технически это разные сбои контракта.
\nЦена ошибки растёт вместе с радиусом общего токена. Локальная правка исправляет один usage. Глобальная правка меняет все usages, включая те, которые не попали в поиск. Если команда не знает список мест и состояний, она не может объяснить diff, выбрать безопасный откат или отличить новый вариант от случайного исключения.
\nТезис. Токен нельзя менять по одному скриншоту. Сначала свяжите симптом с ролью компонента, состоянием, именем токена и известными usages. Затем проверьте допустимость значения. Только после этого меняйте один слой и сохраняйте прежнее значение. Такой порядок делает изменение ограниченным, наблюдаемым и обратимым.
\nДизайн-токен — не просто цветовая константа. Он выражает роль: например, фон primary-кнопки в обычном состоянии. Роль связывает значение с компонентом и состоянием. Если код использует #2457D6 напрямую, он обходит эту связь. Если код ссылается на неизвестное имя, система получает второй, неописанный способ задать ту же роль.
У одного значения есть четыре границы. Первая — имя роли, например button.primary.background. Вторая — состояние: default, hover, focus-visible, disabled или loading. Третья — компонент, который действительно может использовать эту роль. Четвёртая — набор известных мест в коде. Пропуск любой границы превращает косметическую правку в догадку.
Состояния нельзя восстановить из одного цвета. Кнопка может сохранить фон и потерять outline при переходе на клавиатуру. Она может выглядеть одинаково в спокойном состоянии, но стать неразличимой при отключении. Поэтому inventory должен хранить не только имя токена, но и ожидаемые состояния. Для ссылки, переключателя и destructive-действия нужен отдельный контракт, а не необязательный флаг в универсальной кнопке.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| На одном экране другой синий | Literal обошёл именованный токен | Найти значение и сравнить с ролью в inventory | Заменить подтверждённый usage |
| После refactor исчез focus ring | Состояние не входит в матрицу | Пройти кнопку клавиатурой | Вернуть focus-visible в контракт |
| Новое имя не работает | Имя отсутствует в словаре роли | Проверить декларацию и чтение | Остановить правку или добавить роль |
| Review назван visual-проверкой без снимка | Список входов перепутали с результатом | Проверить браузер, viewport и diff | Не выдавать зелёный статус без артефакта |
| Платёжный экран изменился вместе с профилем | Общий токен исправляли без радиуса | Сопоставить usages и роли | Вернуть прежнее значение и отделить variant |
Начните с короткой таблицы известных usages. Для каждого места запишите компонент, роль, состояние, имя и способ задания значения. Например, profile-save использует button.primary.background в состояниях default, hover, focus-visible, disabled и loading. dialog-cancel может быть secondary-кнопкой. Похожая разметка не делает эти usages одной ролью.
Поиск по имени и hex-значению даёт кандидатов, но не доказывает полноту inventory. Стиль может прийти из темы, CSS-переменной, inline-значения или обёртки. Автоматический поиск не определяет, является ли ссылка кнопкой по смыслу. Поэтому результат поиска нужно проверить на уровне entry point и фактического состояния. Учебный пример ниже ограничен тремя usages и не изображает полный обход настоящего репозитория.
\nНеверная конфигурация должна остановиться до записи. В примере есть словарь разрешённых ролей и простая проверка формата: цвет передаётся как #RRGGBB. Это учебное правило, а не универсальный валидатор CSS. Его задача — не дать срочной правке молча создать новый ключ или принять случайную строку.
const tokens = {'button.primary.background':'#2457D6'};\nfunction correctToken(name, nextValue) {\n if (!(name in tokens)) return {ok:false, reason:'unknown-token'};\n if (!/^#[0-9A-F]{6}$/i.test(nextValue)) return {ok:false, reason:'invalid-color'};\n return {ok:true, name, previousValue:tokens[name], nextValue};\n}\nfunction rollbackToken(change) {\n return {ok:change.ok, name:change.name, nextValue:change.previousValue};\n}\nconst change = correctToken('button.primary.background','#1D4ED8');\nconst restored = rollbackToken(change);\nФункция не меняет объект сама. Она возвращает решение с именем, прежним и новым значением. Отдельный слой применяет решение после проверки inventory. Валидатор не знает, подходит ли новый синий для оплаты, профиля или тёмной темы. Он проверяет только форму и существование роли.
\nВ учебном коде change.nextValue равен #1D4ED8, а restored.nextValue равен #2457D6. Пример не запускает CSS, не открывает браузер и не измеряет контраст. Он показывает только две операции: неизвестное имя отклоняется, а принятая правка хранит точное прежнее значение. В настоящем проекте формат, запись и права должны соответствовать его контракту.
До реального запуска можно составить список входов: viewport 375 и 1280, состояния default, hover, focus-visible, disabled, loading, светлая и тёмная темы. Список полезен как граница проверки: он заставляет назвать объём работы и не забыть состояние.
Но список не видит пиксели, cascade, media query, шрифт или порядок фокуса. Заполненный JSON не доказывает, что браузер применил нужную переменную. Для visual-проверки нужны страница, браузер, viewport, baseline, новый снимок, правило допустимого diff и сохранённый результат. Для доступности нужны keyboard route и выбранная комбинация браузера и вспомогательной технологии. Если артефакта нет, проверку нельзя называть успешной.
\nЭтот подход не вычисляет контраст и не заменяет ручную проверку. Он не решает каскад тем, локализацию, media queries, пользовательские настройки, разные браузеры и конкурирующие изменения. Он также не определяет, является ли новый цвет хорошим дизайном. Он отвечает на более узкий вопрос: существует ли роль, что именно она затрагивает и можно ли вернуть прежнее значение.
\nОдин общий токен не всегда лучше двух. Если профиль и оплата имеют разные требования к смыслу, состояниям или риску ошибки, их нужно разделить после подтверждения usages. Нельзя объявлять variant только потому, что один экран случайно выглядит иначе. Сначала исключите literal, неправильное состояние и неверное имя.
\nRollback не безопасен во всех слоях. Возврат значения токена не отменяет опубликованный CSS, не откатывает сборку и не компенсирует действие пользователя. Для этих уровней нужны собственные процедуры и артефакты. Здесь rollback ограничен одной парой name → previousValue.
Правка готова, если команда может показать inventory затронутых usages, матрицу состояний и запись с прежним значением. Неизвестный token и неверный формат останавливаются без изменения. Допустимая правка меняет только выбранную роль. Отдельный запуск подтверждает нужные viewport и состояния реальным артефактом. Повторный просмотр возвращает прежнее значение по сохранённому previousValue, а не по памяти или догадке.
Если visual или accessibility запуск ещё не выполнен, критерий должен прямо это показывать. Нельзя превращать объявленный объём проверки в результат. Для учебного примера достаточно проверить решения функции и отрицательные входы. Для своего интерфейса добавьте браузерную проверку, историю baseline и ссылку на сохранённый diff.
\n