CSS-переменные и дизайн-токены: сравниваем подходы к теме
Кастомные свойства CSS, Sass-переменные и генераторы токенов решают одну задачу по-разному. Разбираем, что выбрать для тёмной темы, ветвления брендов и синхронизации с дизайном.

У вас есть цвет --brand: #2563eb, и завтра дизайнер просит тёмную тему, послезавтра — второй бренд, а через неделю — чтобы токены совпадали с Figma. Один и тот же цвет можно хранить как переменную Sass, как кастомное свойство CSS или как токен, собранный из JSON генератором. Это не три способа сделать одно и то же — у каждого разная цена в рантайме, в поддержке и в интеграции с дизайном. Разбираем, где какой подход выигрывает и чем платит.
Задача, из-за которой возникает вопрос
Пока в проекте один цвет и одна тема, разницы нет: $brand в Sass и --brand в CSS выглядят одинаково. Проблема появляется в момент, когда значение должно меняться во время работы страницы: переключатель тёмной темы, выбор акцентного цвета пользователем, разные бренды на одном коде.
Sass-переменные к этому моменту уже не существуют — они посчитаны на этапе компиляции и вклеены в готовый CSS как литералы. Изменить их из JavaScript нельзя: в браузере их просто нет. Отсюда и развилка.
Что здесь термин, а что нет
Кастомное свойство CSS1 — это переменная уровня самого CSS, живущая в браузере: --name: value объявляется, var(--name) читается. Она наследуется и каскадируется как обычное свойство. Дизайн-токен — не технология, а именованная единица дизайн-решения (цвет, отступ, радиус), которая хранится в источнике правды (обычно JSON) и превращается инструментом в CSS, Sass, iOS, Android. Токен — это про организацию, кастомное свойство — про механизм.
Три подхода
Sass-переменные
Считаются при сборке. В итоговом CSS от них не остаётся следа — только подставленные значения.
// Sass (Dart Sass 1.x)
$brand: #2563eb;
$space-md: 16px;
.button {
background: $brand;
padding: $space-md;
}
// на выходе получится:
// .button { background: #2563eb; padding: 16px; }
Плюс — ноль стоимости в рантайме и мощные вычисления при сборке: циклы, функции, map. Минус — значение зафиксировано навсегда после компиляции. Тёмная тема через Sass делается только дублированием правил под класс, и вес CSS растёт.
Кастомные свойства CSS
Живут в браузере, каскадируются, читаются и меняются из JS.
/* CSS, поддержка во всех актуальных браузерах */
:root {
--brand: #2563eb;
--space-md: 16px;
}
[data-theme="dark"] {
--brand: #60a5fa; /* переопределяем то же имя ниже по каскаду */
}
.button {
background: var(--brand);
padding: var(--space-md);
}
Переключение темы — это смена одного атрибута на <html>, а не пересборка стилей:
// переключатель темы
document.documentElement.setAttribute('data-theme', 'dark');
// или чтение текущего значения
const brand = getComputedStyle(document.documentElement)
.getPropertyValue('--brand').trim();
Плюс — динамика без перекомпиляции и без дублирования блоков. Минус — есть цена в рантайме: браузер пересчитывает var() при изменении. На тысячах узлов, зависящих от одного свойства, смена значения вызывает большой пересчёт стилей. Ещё нюанс — var() не работает в медиавыражениях (@media (min-width: var(--bp)) не сработает), там переменная не подставляется.
Дизайн-токены через генератор
Значения хранятся в JSON, инструмент вроде Style Dictionary собирает из них CSS-переменные, Sass-переменные и форматы для мобильных платформ сразу.
// tokens/color.json (формат Style Dictionary)
{
"color": {
"brand": { "value": "#2563eb" },
"text": { "value": "#111827" }
}
}
// из этого генерируется, например, CSS:
// :root { --color-brand: #2563eb; --color-text: #111827; }
Плюс — один источник правды для веба, iOS и Android и синхронизация с дизайн-системой. Минус — это ещё один шаг сборки и инфраструктура, которую надо поддерживать. Точное имя формата-трансформа сверяйте в документации Style Dictionary вашей версии — набор встроенных трансформов между мажорными версиями менялся.
Сравнение
| Критерий | Sass-переменные | Кастомные свойства CSS | Токены (генератор) |
|---|---|---|---|
| Когда вычисляются | При сборке | В браузере, в рантайме | При сборке → в CSS/другие форматы |
| Меняются из JS | Нет | Да | Через сгенерированные CSS-переменные |
| Тёмная тема | Дублирование правил | Переопределение по каскаду | Тема как отдельный набор токенов |
| Цена в рантайме | Нулевая | Пересчёт стилей при смене | Как у CSS-переменных на выходе |
| Работает в @media | Да | Нет (в условии медиа) | Зависит от целевого формата |
| Синхронизация с дизайном | Нет | Нет | Да, через JSON-источник |
Sass-переменная и кастомное свойство отвечают на разные вопросы: «какое значение подставить в готовый файл» и «какое значение действует прямо сейчас в этом узле дерева». Смешивать их приходится, но выбирать под задачу — обязательно.
Когда что выбирать
- Статичный сайт, тем нет и не будет. Sass-переменные. Ничего не платите в рантайме, вычисления при сборке бесплатны.
- Нужны тёмная тема, пользовательский акцент, переключение на лету. Кастомные свойства CSS. Это единственный из трёх механизмов, который меняется без пересборки.
- Одна дизайн-система на несколько платформ или несколько продуктов. Генератор токенов поверх кастомных свойств: JSON — источник, CSS-переменные — выход для веба.
- Значение нужно и в медиавыражении, и как переменная. Держите брейкпоинты в Sass (они работают в
@media), а цвета и отступы — в кастомных свойствах.
Гибрид — норма, а не компромисс: Sass собирает структуру и брейкпоинты, кастомные свойства несут динамику. Многие дизайн-системы так и устроены.
Что ломается
- Пытаются менять Sass-переменную из JS. Её нет в браузере — меняется только то, что стало кастомным свойством. Частая ошибка при переезде на тёмную тему.
- Кастомное свойство в
@media-условии.@media (min-width: var(--bp))молча не работает: переменная в условии медиа не раскрывается. Брейкпоинты держите на этапе сборки. - Отсутствие фолбэка. Если свойство не объявлено,
var(--x)вернёт пусто и правило поедет. Пишитеvar(--brand, #2563eb)со вторым аргументом. - Массовая смена одного свойства на большом дереве. Если от
--brandзависят тысячи узлов, его смена — заметный пересчёт стилей. Профилируйте вкладкой Performance, а не на глаз. - Токены как ещё одна свалка. Генератор не наводит порядок сам: без соглашения об именах (
--color-brand,--space-md) JSON превращается в тот же хаос, только в новом файле.
Что делать дальше
Начните с малого: вынесите цвета и отступы в кастомные свойства в :root, оставьте брейкпоинты в Sass, добавьте один [data-theme="dark"] с переопределениями. Это даёт рабочую тёмную тему без генератора. Когда наборов тем или платформ станет больше одной, посмотрите Style Dictionary — но не раньше, чем ручное дублирование JSON начнёт мешать. Показатель, за которым стоит следить в проде: длительность recalculate style при переключении темы во вкладке Performance — если она заметна, ищите свойства с широким охватом каскада.
Генератор планировок: Набросок комнаты карандашом → 3D-рендер дизайна.
Спросить за 20 ₽Источники: Using CSS custom properties (variables) — MDN, Style Dictionary — официальная документация
Частые вопросы
А если у меня старый браузер — кастомные свойства сработают?
Переключение темы тормозит — что делать?
Можно ли обойтись без генератора токенов?
Почему var() не работает в моём @media-запросе?
Sass и кастомные свойства — обязательно выбирать одно?
Что писать вторым аргументом var() и зачем?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.