ENGINEERING NOTES / Design System
Design System, которая выдерживает давление продукта
Токены, API, доступность, процессы и изменения, которые не разрушают общую библиотеку.
Статья на русском языкеБиблиотека компонентов — ещё не система
Button, Input, Modal и набор цветов можно собрать относительно быстро. Настоящая проверка начинается позже: маркетинг просит необычный CTA, дизайнер меняет визуальный язык, мобильная команда вводит новые паттерны, а несколько продуктов начинают использовать одни и те же компоненты по-разному. Если каждое требование превращается в очередной флаг или CSS-исключение, библиотека постепенно становится препятствием.
Design System — не только код. Это визуальные правила, токены, интерфейсы компонентов, рекомендации по использованию, документация, правила изменений и распределение ответственности. Её задача — ускорять создание согласованных интерфейсов, не запрещая продукту развиваться. Слишком жёсткая система порождает обходные решения; слишком свободная перестаёт обеспечивать единообразие.
Продуктовое исключение или системная потребность?
Допустим, промостраница требует огромную кнопку с градиентом и анимацией. Можно добавить в базовый Button параметры `isPromo`, `specialGradient`, `largeMobile` и `animated`. Но через несколько месяцев компонент рискует превратиться в набор конфликтующих условий. Разумнее сначала определить, является ли требование общим паттерном или локальной композицией.
Базовая кнопка отвечает за семантику, клавиатуру, состояния, фокус и предсказуемые варианты. Промо-композиция может использовать её как примитив, не заставляя всю систему поддерживать одноразовое оформление. Если одинаковое исключение повторяется у разных команд, это сигнал пересмотреть базовый контракт.
**Вопросы перед добавлением варианта:**
- Повторяется ли сценарий в нескольких продуктах?
- Можно ли собрать его из существующих примитивов?
- Какие новые комбинации параметров появятся?
- Кто будет поддерживать вариант через год?
- Есть ли правила его применения и миграции?
Токены как слой визуальной архитектуры
Значение `#2563eb` говорит о цвете, но ничего не сообщает о назначении. Семантическое имя `color-action-primary` фиксирует роль цвета в продукте. Обычно полезно различать примитивные токены (палитра, шкала размеров), семантические токены (текст, поверхность, действие) и компонентные токены там, где требуется независимая настройка.
При смене бренда или темы компонент продолжает обращаться к смысловому имени. Но не создавайте десятки уровней ссылок ради абстракции: каждое имя должно помогать управлять реальными изменениями. Для обмена токенами между инструментами существует спецификация Design Tokens Community Group; её стабильная версия 2025.10 опубликована как формат сообщества, а не как стандарт W3C Standards Track.
:root {
--blue-600: #2563eb;
--gray-900: #111827;
--white: #fff;
--color-action-primary: var(--blue-600);
--color-text-primary: var(--gray-900);
--color-surface-primary: var(--white);
--button-primary-background: var(--color-action-primary);
--button-primary-text: var(--white);
}
[data-theme='dark'] {
--color-action-primary: #60a5fa;
--color-text-primary: #f9fafb;
--color-surface-primary: #111827;
}
.button--primary {
color: var(--button-primary-text);
background: var(--button-primary-background);
}API компонента — долгосрочный контракт
Набор boolean-флагов вроде `primary`, `danger`, `large`, `compact`, `rounded` быстро допускает противоречивые комбинации. Явные `variant` и `size` позволяют выразить поддерживаемые варианты и упростить типизацию. При этом не стоит превращать базовый компонент в универсальный конструктор любого макета: сложные сценарии лучше решать композицией.
Это сокращённый пример API, а не готовая реализация всех состояний: индикатор загрузки должен сохранять доступное название кнопки, а визуальные состояния требуют отдельной проверки. Для ссылок используйте семантику ссылок, а не кнопку с ручной имитацией навигации. Компонент должен принимать стандартные атрибуты платформы и не смешивать базовый UI с логикой конкретной формы или заказа.
import type { ButtonHTMLAttributes, ReactNode } from 'react';
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
children: ReactNode;
};
export function Button({
variant = 'primary',
size = 'md',
loading = false,
disabled,
children,
...props
}: ButtonProps) {
return (
<button
{...props}
type={props.type ?? 'button'}
disabled={disabled || loading}
aria-busy={loading || undefined}
data-variant={variant}
data-size={size}
>
{children}
</button>
);
}Доступность — свойство общего контракта
Ошибка в одном общем Dialog тиражируется на десятки экранов. Поэтому клавиатурная навигация, фокус, доступное имя, семантика, состояния ошибок и контрастность должны проектироваться вместе с API, а не добавляться после релиза. Для сложных примитивов — Dialog, Combobox, Menu — важно проверять ожидаемые паттерны взаимодействия по WAI-ARIA Authoring Practices и тестировать реальными устройствами ввода.
Автоматические проверки с axe помогают находить часть нарушений, но не заменяют ручные тесты: сценарии работы с экранным диктором, логический порядок фокуса и понятность текстов требуют человеческой оценки. Компонент, прошедший автоматическую проверку, не обязательно удобен в реальном пользовательском сценарии.
**Минимальный набор проверок:** клавиатура без мыши; видимый фокус; корректное название и описание; ошибки формы, связанные с полями; контрастность во всех темах; поведение при увеличенном масштабе и reduced motion.
Документация должна отвечать «когда», а не только «как»
Список пропсов не объясняет, когда выбрать Modal вместо Drawer, какую кнопку считать основным действием и что делать при длинном тексте. Полезная документация показывает назначение, антипаттерны, допустимые варианты, доступность, примеры композиции и ограничения. Storybook позволяет хранить состояния компонентов рядом с кодом и дополнять их документацией.
Для каждого базового компонента полезно описать: назначение; правила выбора; состояния default/hover/focus/disabled/loading/error; responsive-поведение; API; требования доступности; реальные примеры; историю изменений. Документация должна обновляться вместе с компонентом в одном pull request. Иначе она быстро перестаёт быть источником истины.
Дизайн и код: один язык, а не два независимых мира
Figma-библиотека и кодовая библиотека могут расходиться даже при одинаковых названиях. Дизайнер видит один набор вариантов, разработчик — другой; токены копируются вручную, а компоненты выпускаются с разной периодичностью. Согласуйте имена токенов, варианты, состояния и ответственность за изменения. Автоматическая генерация токенов полезна, если команда договорилась о структуре и контролирует результат.
Синхронизация не означает, что любой макет автоматически должен стать новым компонентом. Дизайн-макет может содержать эксперимент, а кодовая библиотека — поддерживаемый контракт. Нужен понятный процесс продвижения решения из эксперимента в систему и обратной связи от разработчиков к дизайнерам.
Версионирование и миграции
Общий пакет становится инфраструктурой: изменение в нём затрагивает множество потребителей. Следуйте осмысленному версионированию, публикуйте changelog и выделяйте breaking changes. Для сложной миграции подготовьте codemod, примеры «до/после» и период совместимости, если он экономически оправдан.
Не путайте номер версии с гарантией качества. Перед выпуском проверяйте публичные типы, визуальные регрессии, сценарии доступности и приложения-потребители. Если компоненты выпускаются независимо, явно документируйте совместимость пакетов и правила обновления.
// Было:
<Button primary large>Сохранить</Button>
// Стало:
<Button variant="primary" size="lg">Сохранить</Button>Качество: визуальные тесты и контрактные проверки
Unit-тест проверит обработчик клика, но может не заметить сломанную сетку или обрезанный текст. Визуальные тесты помогают обнаружить изменения внешнего вида, однако требуют контролируемых шрифтов, размеров экрана и данных, иначе команда утонет в ложных срабатываниях. Дополняйте их функциональными тестами взаимодействия, проверкой доступности и тестированием интеграции в реальных продуктах.
Особенно важны состояния, которые легко забыть: длинные названия, пустые данные, ошибка, загрузка, узкий экран, локализация, увеличенный шрифт и тёмная тема. Демонстрационная кнопка на белом фоне — недостаточное доказательство готовности компонента к продукту.
Управление системой без бюрократии
Нужен понятный путь предложения изменений. Команда продукта описывает проблему и сценарии, владельцы системы проверяют повторяемость и влияние на контракт, затем решение проходит дизайн- и техническое ревью. Маленькие исправления не должны ждать многонедельного комитета, а breaking changes — проходить незаметно.
Ведите список запросов, причины отказов и исключения. Повторяющиеся обходы показывают, где система не соответствует продукту. Полезные показатели — доля использования поддерживаемых компонентов, число дублирующихся реализаций, время внедрения типового сценария и стоимость миграций. Не превращайте процент покрытия компонентами в самоцель: иногда специализированный UI действительно нужен.
План внедрения без остановки продукта
Начните с инвентаризации существующих интерфейсов и выявления повторяющихся паттернов. Определите базовые токены и небольшой набор наиболее востребованных компонентов. Добавьте документацию и проверки. Переведите один реальный продуктовый поток, соберите обратную связь и только затем расширяйте систему.
Не пытайтесь сразу стандартизировать всё. Редко используемый сложный виджет может оставаться продуктовым, пока не появится доказанная потребность в переиспользовании. Успешная система не отменяет разнообразие продукта — она делает повторяющиеся решения надёжными и оставляет место для осмысленных исключений.
Итог
Design System выдерживает давление продукта, когда умеет отличать устойчивые правила от временных требований. Её основа — понятные токены, ограниченные и расширяемые API, встроенная доступность, живая документация, проверяемые релизы и прозрачный процесс изменений. Не количество компонентов, а способность развиваться без накопления бесконечных исключений определяет её зрелость.