Дизайн-токены и темизация

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

Дизайн-токен — это именованная переменная, хранящая одно дизайн-решение (цвет, отступ, размер шрифта). Интерфейс ссылается на токены по имени, а не на «магические» значения.

BEM навёл порядок в именах селекторов. Токены наводят порядок в значениях. Вместо того чтобы в сотне мест писать #3b82f6 и 16px, вы заводите --color-primary и --space-md и ссылаетесь на них. Поменять акцентный цвет всего сайта становится правкой одной строки, а тёмная тема — переопределением набора токенов.

Зачем это на практике

Без токенов значения расползаются: дизайнер использует пять оттенков серого, в коде их оказывается двенадцать слегка разных, отступы скачут 14px/15px/16px без причины. Это и есть «дрейф дизайна». Токены делают набор значений конечным и осознанным: есть шкала отступов, палитра, типографическая шкала — и всё интерфейс собирается только из них. Плюс появляется бесплатная суперсила — темизация: переопределили токены, и весь UI перекрасился, ни одного компонента трогать не пришлось.

CSS-переменные как система

Технически токены в CSS — это кастомные свойства (--имя), объявленные на :root и применяемые через var(). Объявляют их по группам — цвета, отступы, типографика:

:root {
  /* Палитра — «сырые» цвета */
  --blue-500: #3b82f6;
  --gray-100: #f3f4f6;
  --gray-900: #111827;

  /* Семантические токены — назначение, а не цвет */
  --color-bg: #ffffff;
  --color-text: var(--gray-900);
  --color-primary: var(--blue-500);

  /* Шкала отступов */
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;

  /* Типографика */
  --font-body: system-ui, sans-serif;
  --text-base: 1rem;
  --text-lg: 1.25rem;
}

Дальше компоненты ссылаются только на токены:

.button {
  background: var(--color-primary);
  color: var(--color-bg);
  padding: var(--space-sm) var(--space-md);
  font: var(--text-base) var(--font-body);
}

Два слоя токенов: «сырые» и семантические

Хороший приём — разделять палитру (--blue-500 — «какой это цвет») и семантику (--color-primary — «для чего цвет»). Компоненты используют семантические токены, а семантические указывают на палитру. Тогда при смене темы вы переопределяете тонкий семантический слой, не трогая ни палитру, ни компоненты. Это ровно то, что делают зрелые дизайн-системы.

Светлая и тёмная тема через токены

Поскольку var() вычисляется в момент применения, достаточно переопределить семантические токены в другом контексте — и весь интерфейс перекрасится. Удобный способ — атрибут на корне:

:root {
  --color-bg: #ffffff;
  --color-text: #111827;
  --color-primary: #3b82f6;
}

[data-theme="dark"] {
  --color-bg: #0b1120;
  --color-text: #e5e7eb;
  --color-primary: #60a5fa;
}

Компоненты не меняются ни на строку — они и так читают var(--color-bg). Переключение темы сводится к смене одного атрибута на <html>, например через крошечный скрипт по клику на тумблер. Можно автоматически следовать системной теме:

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #0b1120;
    --color-text: #e5e7eb;
  }
}

Согласованность дизайна

Когда весь UI собран из токенов, согласованность перестаёт быть вопросом аккуратности и становится встроенным свойством. Все отступы кратны шкале, все цвета — из палитры, все размеры текста — из типографической шкалы. Хочется «чуть больше воздуха» между блоками — не подбираешь 17px на глаз, а берёшь следующий токен --space-lg. Решения принимаются один раз, на уровне токенов, и автоматически распространяются по всему продукту.

Связь с дизайн-системами

Дизайн-токены — это мост между дизайнером и кодом. В инструментах вроде Figma те же сущности называются «переменными» / «токенами», и значение color/primary там — это тот же --color-primary здесь. Зрелые команды держат единый список токенов как источник правды и выгружают его в разные форматы: CSS-переменные для веба, JSON или платформенные константы для мобильных. Существует даже отраслевой формат описания токенов (W3C Design Tokens), чтобы один набор можно было использовать на всех платформах. Для нас ключевая мысль проще: токен — общий словарь, на котором дизайн и разработка говорят об одном и том же.

Как это работает под капотом

CSS-переменные — это не препроцессорная подстановка, а полноценные живые значения времени выполнения. Браузер вычисляет var(--x) в момент применения свойства и берёт значение из ближайшего предка, где токен определён, — то есть они каскадируются и наследуются, как обычные свойства. Поэтому переопределение --color-bg внутри [data-theme="dark"] действует на всё поддерево этого элемента. И поэтому же темы переключаются мгновенно без пересборки: меняется атрибут — браузер заново вычисляет var() и перекрашивает страницу. Этим CSS-переменные принципиально отличаются от переменных Sass, которые «впекаются» в значения ещё на этапе сборки и в браузере уже не существуют (об этом — в следующем уроке).

Частые ошибки

  • Токены-цвета называют по цвету, а не по роли. --blue плохо переживает редизайн: если синий станет фиолетовым, имя соврёт. Семантические токены называйте по назначению: --color-primary, --color-danger.
  • Смешивают палитру и семантику в один слой. Когда компоненты ссылаются прямо на --blue-500, темизация ломается. Компоненты должны видеть только семантические токены.
  • Оставляют «магические» значения мимо системы. Один захардкоженный #2a2a2a в тёмной теме останется светлым/тёмным невпопад. Если значение влияет на тему — оно должно быть токеном.
  • Путают CSS-переменные с Sass-переменными. Sass-переменная ($color) вычисляется при сборке и не умеет в рантайм-темы; для переключаемых тем нужны именно CSS-кастомные свойства (--color).
  • Забывают значение по умолчанию там, где токен может отсутствовать. var(--gap, 16px) подставит запасное значение, если токен не определён, — полезно для переносимых компонентов.

Итоги

  • Дизайн-токен — именованная переменная одного дизайн-решения; в CSS это кастомное свойство --имя, читаемое через var().
  • Делите токены на два слоя: «сырая» палитра (--blue-500) и семантика по назначению (--color-primary); компоненты используют только семантику.
  • Темизация — это переопределение семантических токенов в другом контексте ([data-theme="dark"] или prefers-color-scheme); компоненты не меняются.
  • Сборка UI только из токенов делает согласованность встроенным свойством, а не вопросом дисциплины.
  • Токены — общий словарь дизайна и кода (Figma-переменные ↔ CSS-переменные); CSS-переменные живут в рантайме и каскадируются, в отличие от Sass-переменных.
Проверьте себя
1. Почему для переключаемой светлой/тёмной темы нужны именно CSS-переменные (--color), а не Sass-переменные ($color)?
ASass-переменные вообще нельзя использовать для цветов
BCSS-переменные вычисляются в браузере во время выполнения и каскадируются, поэтому их можно переопределить для темы без пересборки
CCSS-переменные занимают меньше памяти
DSass-переменные работают только в старых браузерах
2. В чём смысл деления токенов на «сырую» палитру (--blue-500) и семантический слой (--color-primary)?
AТак файл стилей становится короче
BСемантический слой можно переопределять под тему, не трогая палитру и компоненты, которые ссылаются только на него
CБраузер быстрее находит переменные с длинными именами
DЭто требование стандарта CSS