8 lines
14 KiB
JSON
8 lines
14 KiB
JSON
{
|
||
"index": 204,
|
||
"slug": "editorial-2022-05-practice-design-system",
|
||
"title": "Маленькая дизайн-система: начать с контракта кнопки, а не с каталога компонентов",
|
||
"excerpt": "Практический маршрут для одинаковых кнопок, которые разошлись по цвету, состояниям и семантике: named tokens, один минимальный contract, usage inventory и обратимая правка.",
|
||
"contentHtml": "<p>Три одинаковые на вид кнопки редко ломаются одновременно. Одна берёт синий цвет из локального файла, вторая не показывает фокус, третья на disabled меняет только opacity, а четвёртая вместо понятного имени имеет иконку. Симптом кажется косметическим, пока пользователь не попадает в другой сценарий. Цена — каждый новый экран получает ещё один почти такой же control, а исправление цвета начинает менять поведение там, где его не ожидали.</p>\n<p>В мае 2022 года я бы не начинал с «универсальной дизайн-системы». Сначала нужен узкий контракт одной primary button: именованные токены, обязательные состояния, семантическое имя, роль и список реальных мест использования. Это продолжает предыдущую статью о доступном control: внешний вид и name/role/state нельзя держать в разных случайных ветках. Пример ниже — локальная fixture, не DOM, не CSS и не visual regression test; он проверяет только объявленные данные.</p>\n<h2>Выбрать границу: одна кнопка, а не вся библиотека</h2>\n<p>Минимальная система отвечает на вопрос «какой button contract должны разделять эти три места», а не «как описать любой интерфейс». В неё входят пять токенов: background, foreground, focus ring, radius и gap. В неё входят пять состояний: default, hover, focus-visible, disabled и loading. Не все состояния обязаны выглядеть одинаково в каждом продукте, но их отсутствие не должно быть случайностью. Если loading невозможен для действия без сети, это решение нужно записать в contract, а не скрыть в одном компоненте.</p>\n<div class=\"table-scroll\"><table><caption>Минимальный контракт primary button</caption><thead><tr><th scope=\"col\">Часть</th><th scope=\"col\">Симптом без неё</th><th scope=\"col\">Проверяемый факт</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Named token</td><td>два экрана называют один синий разными hex-значениями</td><td>у каждого required value есть стабильное имя</td><td>вынести значение в button.primary.* и искать локальные дубли</td></tr><tr><td>State matrix</td><td>focus или loading появляется только после жалобы</td><td>default, hover, focus-visible, disabled, loading названы до реализации</td><td>для отсутствующего state принять явное product decision</td></tr><tr><td>Semantic contract</td><td>иконка выглядит как кнопка, но не имеет понятного действия</td><td>есть name, role button и declared state</td><td>оставить native host, если custom behavior не нужен</td></tr><tr><td>Usage inventory</td><td>правка profile-save ломает оплату</td><td>перечислены известные usage с контекстом и состоянием</td><td>менять один token малым diff и повторять проверку мест</td></tr></tbody></table></div>\n<h2>Токен — имя решения, а не переменная ради переменной</h2>\n<p>CSS Custom Properties допускает author-defined properties с префиксом <code>--</code> и подстановку через <code>var()</code>. Это полезный механизм, но он не создаёт за команду словарь design tokens. Имя <code>button.primary.background</code> в этой статье — соглашение пакета: оно говорит, что значение относится к роли primary button, а не ко всем синим пикселям проекта. Поэтому не стоит сразу делать <code>brand.blue.500</code> единственным входом для компонента: у роли должна быть собственная граница, даже если сегодня она ссылается на тот же цвет.</p>\n<p>Проверка проста: у каждой величины есть имя, владелец и место потребления. Если в pull request появляется <code>#2457D6</code> рядом с кнопкой, сначала спросите, это новый token или обход существующего. Если ответ «временно», зафиксируйте срок и конкретный rollback. Не нужно объявлять каждую тень и каждый margin глобальным token. Глобальность оправдана только повторяемым contract; одиночная геометрия остаётся локальной, пока не появится второй подтверждённый use case.</p>\n<h2>Учебная fixture: проверить данные до сборки CSS</h2>\n<p>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 не запускались.</p>\n<pre><code>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.</code></pre>\n<p>Такой тест ловит дешёвую ошибку раньше рендера: кто-то добавил usage без имени, убрал loading из состояния или стал использовать token, которого contract не объявляет. Он не ловит контраст на реальном фоне, порядок клавиатуры, cascade в существующем CSS или изменение пикселей на устройстве. Это разные проверки. Их полезно добавлять следующими, но нельзя дорисовывать их результат к локальному объекту числом score или словом «доступно».</p>\n<figure><img src=\"/assets/editorial/2022/design-system-token-flow-2022.svg\" alt=\"Поток малого button contract: пять named tokens поступают в компонент primary button с пятью обязательными состояниями; затем usage inventory перечисляет профиль, оплату и диалог; справа visual payload объявляет viewports и states, но помечен как не являющийся screenshot или test result.\" loading=\"lazy\" /><figcaption>Схема отделяет источник значения, contract компонента и будущий вход visual-проверки. Между ними нет выдуманного production-результата.</figcaption></figure>\n<h2>Маршрут: симптом → причина → проверка → действие</h2>\n<ol><li><strong>Симптом.</strong> Найдите одну повторяющуюся кнопку, у которой расходятся color, focus или label. Не группируйте сразу все controls.</li><li><strong>Причина.</strong> Выпишите, где лежат literal values, состояния и semantic fields. Обычно они принадлежат разным локальным файлам без общего contract.</li><li><strong>Проверка.</strong> Соберите usage inventory: context, name, role, состояние, локальные overrides. Затем запустите fixture и убедитесь, что declared payload не называют результатом visual test.</li><li><strong>Действие.</strong> Внесите один named token и одну state matrix для primary button. Оставьте native button там, где не требуется другой host.</li><li><strong>Откат.</strong> Сохраните прежнее значение token до правки. Если один usage изменился неожиданно, верните только token и разберите его локальный override.</li><li><strong>Следующая проверка.</strong> После contract запустите отдельную реальную visual и a11y-проверку в согласованной среде. Её артефакт должен содержать версии и наблюдения.</li></ol>\n<h2>Где маленький contract заканчивается</h2>\n<p>Этот подход не выбирает типографику бренда, не строит темизацию, не мигрирует legacy CSS и не заменяет дизайн-ревью. Он также не доказывает WCAG-conformance: WCAG содержит проверяемые критерии, но локальная fixture не наблюдает страницу. Числа, hex-значения и названия из примера — учебные проектные решения. В другом продукте focus ring может иметь другое имя и значение; важнее, чтобы его существование и ответственность были явными.</p>\n<p>Следующий проверяемый шаг — выбрать три настоящих usage одной primary button, составить inventory до изменения и договориться о минимальном payload для внешнего visual review. Если один usage требует другого состояния или семантики, не расширяйте contract по умолчанию. Сначала зафиксируйте причину: это variant той же кнопки или другой control. Такой вопрос экономит больше времени, чем ранний каталог из двадцати компонентов.</p>\n<h2>Историческая граница мая 2022</h2>\n<p>Текст опирается на 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 существовали в конкретной команде.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.w3.org/TR/2021/CRD-css-variables-1-20211111/\" target=\"_blank\" rel=\"noopener noreferrer\">CSS Custom Properties for Cascading Variables Module Level 1, Candidate Recommendation Draft от 11 ноября 2021 года</a> — датированный нормативный снимок: custom properties имеют имена --* и подставляются через var(). Он не определяет taxonomy дизайн-токенов и не подтверждает результат visual regression.</li><li><a href=\"https://www.w3.org/TR/2021/CRD-wai-aria-1.2-20211208/\" target=\"_blank\" rel=\"noopener noreferrer\">WAI-ARIA 1.2, Candidate Recommendation Draft от 8 декабря 2021 года</a> — датированный нормативный снимок, доступный в мае 2022 года. Он описывает роли и состояния, но не выбирает за продукт токены, текст кнопки или набор variants.</li><li><a href=\"https://www.w3.org/TR/2018/REC-WCAG21-20180605/\" target=\"_blank\" rel=\"noopener noreferrer\">Web Content Accessibility Guidelines 2.1, Recommendation от 5 июня 2018 года</a> — неизменяемая W3C Recommendation с проверяемыми критериями, включая Keyboard, Focus Visible, Name/Role/Value и Non-text Contrast. Fixture пакета их не измеряет.</li></ul>"
|
||
}
|