From b58b5135f1a7c014e51580560862b643afe0bd6f Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 19:47:38 +0300 Subject: [PATCH] editorial: refine article 204 --- editorial/agent-rewrites/204.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editorial/agent-rewrites/204.json b/editorial/agent-rewrites/204.json index 5d026a0..32763dd 100644 --- a/editorial/agent-rewrites/204.json +++ b/editorial/agent-rewrites/204.json @@ -1,7 +1,7 @@ { "index": 204, "slug": "editorial-2022-05-practice-design-system", - "title": "Маленькая дизайн-система: как закрепить контракт кнопки", - "excerpt": "Кнопки расходятся по цвету, фокусу и поведению, когда их контракт разбросан по разным слоям. Разбираем малую схему с токенами, состояниями, проверками и обратимой правкой.", - "contentHtml": "

Проблема: на одном экране primary button получает цвет из локального CSS, на другом теряет видимый фокус, а на третьем остаётся доступной во время загрузки; пользователь видит непредсказуемое действие, а команда платит повторной правкой и риском сломать соседний сценарий.

\n

Причина обычно не в том, что в проекте мало компонентов. У одной кнопки нет общего проверяемого контракта. Цвет живёт в стилях, состояние — в обработчике, текст — в разметке, а список мест использования никто не держит. Поэтому безопасная на вид замена цвета может затронуть оплату, профиль и диалог одновременно.

\n

Начинайте с одной повторяющейся primary button. Зафиксируйте её роль, значения, состояния и известные места применения. Такой малый contract не заменяет всю дизайн-систему. Он ограничивает изменение так, чтобы его можно было проверить и откатить.

\n

Тезис: сначала договор, потом каталог

\n

Каталог компонентов полезен, когда команда уже несколько раз приняла одно и то же решение. До этого каталог часто только прячет расхождения за общим названием. Две кнопки могут называться Button, но иметь разные правила loading, разные accessible name и разные последствия для отправки формы.

\n

Малый контракт отвечает на четыре вопроса. Какое значение меняется? Какие состояния поддерживает control? Как пользователь понимает его смысл? Какие существующие места попадут под правку? Если на один вопрос нет ответа, компонент ещё нельзя считать единым.

\n

Механизм малого контракта

\n

Разделите данные на четыре слоя. Named tokens хранят значения и их роль. State matrix перечисляет допустимые состояния. Semantic fields описывают имя и поведение control. Usage inventory показывает известный радиус изменения. Каждый слой проверяется отдельно, но вместе они дают узкий договор.

\n

Для primary button достаточно начать с пяти token names: button.primary.background, button.primary.foreground, button.primary.focus-ring, button.primary.radius и button.primary.gap. Названия в примере — учебное соглашение. В настоящем проекте они должны соответствовать принятой системе именования.

\n

State matrix может содержать default, hover, focus-visible, disabled и loading. Не каждое действие обязано поддерживать каждую ветку. Но отсутствие состояния должно быть решением. Если операция синхронная и loading ей не нужен, это нужно записать. Пустая ветка, которую команда просто забыла, станет дефектом позже.

\n

Semantic fields не выводятся из цвета. Серый цвет может означать disabled, loading, неактивную вкладку или случайный override. Контракт хранит имя действия, роль, объявленное состояние и признак disabled отдельно. Для обычного действия выбирайте native button: браузер уже даёт базовую роль и клавиатурную модель.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Один синий цвет задан разными hex-значениямиЗначение не связано с ролью компонентаНайти literal values и сравнить их usageВвести один named token для primary button
Фокус или loading появляется только после жалобыСостояния не объявлены заранееСверить state matrix с кодом и сценариямиДобавить состояние или явно исключить его
Иконка выглядит как кнопка, но действие неясноИмя control выводят из картинкиПроверить текстовое имя и native semanticsОставить текст или задать проверяемое accessible name
Правка профиля меняет оплатуНеизвестны точки примененияСоставить inventory с контекстом и состояниемСузить diff и повторить проверку мест
\n

Токен задаёт роль, а не просто переменную

\n

CSS Custom Properties дают именованные свойства и подстановку через var(). Браузер понимает их синтаксис, но не знает, что значение относится к primary button и кто отвечает за его изменение. Смысл появляется в соглашении команды и в повторяемом применении.

\n

Например, --button-primary-background можно передать компоненту как CSS custom property. Но это не означает, что любой синий фон должен использовать тот же token. Primary, destructive и secondary button имеют разные роли, даже если сегодня два значения совпадают. Роль помогает менять одно решение без неожиданного каскадного эффекта.

\n

При проверке ищите четыре связи: имя token, значение, владелец и потребители. Если рядом с компонентом появляется новый #2457D6, решите, что это: новый вариант, локальное исключение или обход существующего contract. Временное значение тоже получает срок и способ отката. Иначе временное исключение быстро станет вторым стандартом.

\n

Конкретный пример

\n

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

\n
<button\n  type=\"submit\"\n  class=\"button button--primary\"\n  data-state=\"default\"\n>\n  Сохранить изменения\n</button>
\n

В реальном интерфейсе нужно проверить переходы default → loading → default или error. Пока запрос выполняется, повторная отправка должна иметь явное правило. После ошибки пользователь должен понимать, что произошло и какое действие доступно дальше. Эти решения нельзя прятать только в opacity.

\n

Если вместо native element используется div role=\"button\", одной ARIA-роли недостаточно. Понадобятся клавиатурное управление, focus behavior, disabled semantics и проверка имени. Если custom host не даёт нужного поведения, это отрицательный результат: вернитесь к native button, а не добавляйте ещё один слой стилизации.

\n

Проверка данных до проверки страницы

\n

Сначала можно проверить сам контракт: все ли обязательные token names объявлены, все ли состояния перечислены, есть ли у control имя и роль, заполнены ли известные usage. Такой тест быстро ловит пропущенное поле и не требует рендера.

\n
const requiredStates = [\n  'default', 'hover', 'focus-visible', 'disabled', 'loading'\n];\n\nconst contract = {\n  name: 'Сохранить изменения',\n  role: 'button',\n  state: 'default',\n  permittedStates: requiredStates\n};\n\nconst missingStates = requiredStates.filter(\n  (state) => !contract.permittedStates.includes(state)\n);\n\nconst ok = missingStates.length === 0\n  && contract.role === 'button'\n  && Boolean(contract.name);
\n

Результат true здесь означает только согласованность объекта. Код не создаёт DOM, не собирает CSS, не измеряет контраст, не проверяет порядок фокуса и не говорит, что screen reader произнесёт ожидаемое имя. Не называйте такую проверку visual regression или доказательством соответствия WCAG. Для этих утверждений нужна наблюдаемая страница и отдельный артефакт.

\n
\"Схема
Схема разделяет значения, состояния, семантику и места применения. Объявленный contract не является снимком интерфейса и не заменяет проверку в браузере.
\n

Порядок действий

\n
  1. Выберите один control. Найдите повторяющуюся primary button с расхождением цвета, focus или label. Не включайте сразу все кнопки.
  2. Опишите симптом. Запишите экран, состояние и действие пользователя. Отделите наблюдаемую разницу от гипотезы о причине.
  3. Составьте inventory. Для каждого найденного usage укажите контекст, имя, роль, состояние и локальные overrides. Поиск по коду остаётся отдельной проверкой полноты.
  4. Объявите contract. Назовите tokens и state matrix. Для каждой неприменимой ветки запишите решение.
  5. Проверьте данные. Убедитесь, что нет пропущенных token, состояния или semantic name. Зафиксируйте, чего этот тест не наблюдает.
  6. Проверьте страницу. Соберите CSS, пройдите клавиатурный сценарий, проверьте DOM и accessibility tree, затем выполните согласованную visual-проверку на нужных viewport.
  7. Сделайте правку обратимой. Сохраните прежнее значение token и список затронутых usage. При неожиданном эффекте откатите точечную правку, а не весь unrelated CSS.
\n

Отрицательный путь: похожий control не всегда тот же

\n

Предположим, три формы используют разные обязательные поля, цвета ошибок и правила disabled. Общий token не исправит проблему. Эти формы могут выглядеть похоже, но иметь разные state matrix и разные требования к имени. В таком случае не расширяйте primary button новым набором optional props только ради повторного названия.

\n

Сначала зафиксируйте различие в поведении. Если оно устойчиво, создайте отдельную роль или variant с собственным contract. Если различие появилось из-за случайного override, удалите override и верните control к исходному contract. Это дешевле, чем превращать исключение в универсальный API.

\n

Ограничения и критерий готовности

\n

Малый contract не строит темизацию, не мигрирует legacy CSS, не выбирает типографику бренда и не доказывает, что найден каждый usage. Он также не заменяет ручную проверку доступности и visual comparison. Значения, имена и места из примеров учебные. Production-результат можно утверждать только после реального запуска и сохранённого наблюдения.

\n

Критерий готовности проверяемый: для выбранной primary button каждый обязательный state имеет решение; каждый token имеет имя и место потребления; каждый найденный usage имеет name, role и state; проверка данных проходит; реальная страница отдельно подтверждает keyboard, focus-visible, DOM semantics и visual diff; rollback возвращает прежнее значение. Если хотя бы один пункт неизвестен, contract не готов.

\n

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

\n" + "title": "Маленькая дизайн-система: начать с контракта кнопки, а не с каталога компонентов", + "excerpt": "Практический маршрут для одинаковых кнопок, которые разошлись по цвету, состояниям и семантике: named tokens, один минимальный contract, usage inventory и обратимая правка.", + "contentHtml": "

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

\n

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

\n

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

\n

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

\n
Минимальный контракт 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 и повторять проверку мест
\n

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

\n

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

\n

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

\n

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

\n

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 не запускались.

\n
node web/scripts/upgrade-2022-05.mjs --verify-fixture\n// PASS fixture: 13/13 assertions\n\n// Проверяется локальный contract: tokens, states, semantic fields, inventory,\n// declared visual payload, invalid configuration и rollback. Это не UI test.
\n

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

\n
\"Поток
Схема отделяет источник значения, contract компонента и будущий вход visual-проверки. Между ними нет выдуманного production-результата.
\n

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

\n
  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-проверку в согласованной среде. Её артефакт должен содержать версии и наблюдения.
\n

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

\n

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

\n

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

\n

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

\n

Текст опирается на 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 существовали в конкретной команде.

\n

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

\n" }