CSS: переменные и темы
Введение: один источник стилей для всего интерфейса
Когда в проекте десятки экранов, одни и те же цвета, отступы и размеры начинают дублироваться.
Без системы любое изменение превращается в поиск по сотням строк.
Без системы любое изменение превращается в поиск по сотням строк.
CSS-переменные решают это как "единый пульт управления": меняете значение в одном месте, и интерфейс обновляется везде.
На этой базе строятся темы, дизайн-токены и стабильная поддержка проекта.
На этой базе строятся темы, дизайн-токены и стабильная поддержка проекта.
💡 Совет:
Сначала выделите базовые токены (
color, space, radius), потом начинайте усложнять тему.✅ Вывод:
Переменные в CSS нужны для управления стилем на уровне системы, а не отдельных блоков.
Проблема -> решение
Проблема:
Фиксированные значения "вшиты" в компоненты: тема меняется долго, стили расходятся, а поддержка дорожает.
Решение:
Вынести ключевые значения в CSS custom properties, задавать их иерархически (глобально и локально) и строить тему через переопределение токенов.
✅ Вывод:
Переменные превращают разрозненный CSS в управляемую архитектуру стилей.
Чем помогает и как работает
CSS-переменные помогают:
- снизить дублирование значений по проекту;
- ускорить запуск светлой/темной и брендовых тем;
- делать компоненты переиспользуемыми в разных контекстах;
- уменьшить риск "сломать весь UI" при изменении цвета или размера.
Как это работает:
- Шаг 1: определяете базовые дизайн-токены (
--color-bg,--space-m,--radius-m). - Шаг 2: объявляете их на уровне
:rootкак глобальные значения. - Шаг 3: используете токены в компонентах через
var(...). - Шаг 4: локально переопределяете переменные внутри нужного контейнера.
- Шаг 5: создаете тему через
data-themeили media query и меняете только токены. - Шаг 6: добавляете fallback-значения для устойчивости компонентов.
- Шаг 7: фиксируете правила именования и структуру токенов для команды.
✅ Вывод:
Тема на CSS-переменных это не магия, а дисциплина работы с общими значениями.
Ключевые термины (простыми словами)
- Custom properties (CSS-переменные) - значения, которые можно переиспользовать.
:root- корневой селектор для глобальных переменных.var()- функция, которая подставляет значение переменной.- Fallback (резерв) - запасное значение в
var(--token, reserve). - Scope (область действия) - где переменная видна и применяется.
- Design tokens (дизайн-токены) - стандартизированные значения интерфейса.
- Theme (тема) - набор токенов для конкретного визуального режима.
1. Глобальные токены через :root
Назначение:
Задать базовые значения, общие для всего приложения.
Простыми словами:
Это "главная таблица стилей", из которой берут цвета и размеры все компоненты.
Для новичка:
Если значение повторяется в нескольких местах, ему почти всегда нужен токен в
:root.Аналогия:
Единый склад материалов для всей стройки.
Пример:
:root { --color-bg: #ffffff; --color-text: #111111; --color-accent: #2563eb; --space-m: 16px; --radius-m: 12px;}🔎 Как это происходит на практике:
- В проекте есть кнопки, карточки и формы.
- Все они берут цвет текста из
--color-text. - Дизайнер меняет тон текста, разработчик правит одно значение в
:root.
Характеристики:
- ✅ единый источник правды для стилей;
- ✅ быстрые массовые изменения;
- ✅ легче поддержка и ревью.
Когда использовать:
Всегда для базовых цветовых, пространственных и типографических токенов.
✅ Вывод:
:root это точка старта любой стабильной темы.2. var() и fallback-значения
Назначение:
Подставлять токены в свойства и страховать компонент резервным значением.
Простыми словами:
var() берет значение из переменной, а fallback спасает, если переменной нет.Для новичка:
Пишите fallback в библиотечных компонентах, которые могут жить вне вашего приложения.
Аналогия:
Если основной ключ не подошел, используем запасной.
Пример:
.badge { color: var(--color-accent, #2563eb); border-radius: var(--radius-m, 10px);}🔎 Как это происходит на практике:
- Компонент "badge" вынесли в отдельный пакет.
- В новом проекте токен
--color-accentпока не определен. - Fallback не дает компоненту сломаться визуально.
Характеристики:
- ✅ повышает устойчивость UI-компонентов;
- ✅ упрощает интеграцию между проектами;
- ✅ снижает риск "прозрачного" текста и пустых стилей.
Когда использовать:
В shared-компонентах и там, где токен может быть не объявлен.
✅ Вывод:
Fallback это базовая страховка для надежного CSS.
3. Scope и локальные переменные
Назначение:
Переопределять глобальные токены точечно внутри конкретного блока.
Простыми словами:
Вы можете дать одной карточке свой фон, не ломая остальные карточки.
Для новичка:
Переменная, заданная в блоке, действует внутри него и его потомков.
Аналогия:
Общие правила для здания и локальные правила для одной комнаты.
Пример:
.card { --card-bg: #ffffff; background: var(--card-bg);} .card--accent { --card-bg: #eef2ff;}🔎 Как это происходит на практике:
- Есть базовая карточка курса.
- Для промо-карточки нужен другой фон.
- Вместо дублирования стилей переопределяют только локальную переменную.
Характеристики:
- ✅ минимум дублирования CSS;
- ✅ удобно для модификаторов;
- ✅ сохраняется читаемость структуры.
Когда использовать:
Для вариаций компонента (accent, warning, compact, premium).
✅ Вывод:
Scope делает тему гибкой без хаоса в классах.
4. Темы через data-theme и переопределение токенов
Назначение:
Переключать визуальный режим интерфейса без переписывания всех компонентов.
Простыми словами:
Компоненты остаются теми же, меняется только набор значений переменных.
Для новичка:
Лучше переключать тему на уровне
html или body, а не в каждом компоненте отдельно.Аналогия:
Сменить фильтр цвета для всей сцены одним переключателем.
Пример:
:root { --bg: #ffffff; --text: #111111; --accent: #2563eb;} [data-theme="dark"] { --bg: #0b0f19; --text: #f9fafb; --accent: #60a5fa;} body { background: var(--bg); color: var(--text);}🔎 Как это происходит на практике:
- На странице есть toggle "Light / Dark".
- JS меняет
data-themeу<html>. - Компоненты автоматически получают новые цвета через токены.
Характеристики:
- ✅ быстрое переключение темы;
- ✅ минимальные изменения в CSS компонентов;
- ✅ проще поддерживать несколько брендов.
Когда использовать:
Для светлой/темной темы, white-label проектов и брендовых палитр.
✅ Вывод:
Тема это переопределение токенов, а не копия всей таблицы стилей.
5. prefers-color-scheme и системная тема
Назначение:
Учитывать системные предпочтения пользователя автоматически.
Простыми словами:
Если у пользователя в системе темная тема, сайт может подстроиться сам.
Для новичка:
Это хороший базовый слой, даже если у вас есть ручной переключатель.
Аналогия:
Интерфейс подстраивает освещение под режим комнаты.
Пример:
@media (prefers-color-scheme: dark) { :root { --bg: #0b0f19; --text: #f9fafb; }}🔎 Как это происходит на практике:
- Пользователь открывает сайт ночью с включенной dark-темой ОС.
- Базовая палитра автоматически уходит в темный режим.
- UX кажется "родным" и менее утомляющим для глаз.
Характеристики:
- ✅ работает без JS;
- ✅ повышает комфорт;
- ✅ улучшает доступность.
Когда использовать:
Как минимум для автоматической базовой темы в любом современном интерфейсе.
✅ Вывод:
Системная тема это ожидание пользователя, а не редкий бонус.
6. Именование токенов и структура темы
Назначение:
Держать токены понятными и стабильными в долгой поддержке.
Простыми словами:
Название токена должно сразу объяснять смысл, а не конкретный цвет.
Для новичка:
Лучше
--color-text-primary, чем --blue-500, если токен отвечает за роль в интерфейсе.Аналогия:
Подписанные ящики на складе быстрее находить и сложнее перепутать.
Пример:
:root { --color-surface: #ffffff; --color-text-primary: #111111; --color-text-secondary: #6b7280; --color-action-primary: #2563eb;}🔎 Как это происходит на практике:
- В проект приходит новый разработчик.
- По именам токенов он быстро понимает, что за что отвечает.
- На ревью меньше споров и случайных замен.
Характеристики:
- ✅ предсказуемость для команды;
- ✅ легче масштабировать дизайн-систему;
- ✅ меньше "магических" значений в коде.
Когда использовать:
Всегда, когда токены растут больше 10-15 штук.
✅ Вывод:
Хорошее именование токенов экономит месяцы поддержки.
7. calc(), clamp() и динамические токены
Назначение:
Строить гибкие размеры на основе базовых переменных.
Простыми словами:
Один токен может управлять целым ритмом отступов и типографики.
Для новичка:
Начинайте с одного базового spacing-токена и умножайте его через
calc().Аналогия:
Один модуль конструктора, из которого собираются разные размеры.
Пример:
:root { --space: 8px; --font-base: 16px;} .section { padding: calc(var(--space) * 3);} h1 { font-size: clamp(1.5rem, 2vw, 2.25rem);}🔎 Как это происходит на практике:
- Команда решает увеличить "плотность" интерфейса.
- Меняют базовый
--spaceс 8px на 6px. - Большинство отступов автоматически перестраивается.
Характеристики:
- ✅ единый масштаб размеров;
- ✅ меньше ручных правок;
- ✅ проще адаптивность.
Когда использовать:
Для систем отступов, размеров элементов и типографики.
✅ Вывод:
Динамические токены делают тему управляемой, а не статичной.
8. Переключение темы в runtime и сохранение выбора
Назначение:
Дать пользователю управлять темой и помнить его выбор между сессиями.
Простыми словами:
Человек выбрал темную тему, перезагрузил страницу, тема осталась.
Для новичка:
CSS отвечает за токены, JS обычно отвечает за изменение
data-theme и сохранение в localStorage.Аналогия:
Запомнить любимый режим освещения и включать его автоматически.
Пример:
html[data-theme="light"] { color-scheme: light; }html[data-theme="dark"] { color-scheme: dark; }const root = document.documentElement;const next = root.dataset.theme === "dark" ? "light" : "dark";root.dataset.theme = next;localStorage.setItem("theme", next);🔎 Как это происходит на практике:
- На сайте есть кнопка "Сменить тему".
- При клике меняется
data-theme. - Выбор пользователя сохраняется и применяется при следующем входе.
Характеристики:
- ✅ улучшает персонализацию;
- ✅ поддерживает долгие пользовательские сессии;
- ✅ снижает повторные настройки при каждом посещении.
Когда использовать:
Если продуктом пользуются регулярно и тема влияет на комфорт чтения.
✅ Вывод:
Runtime-переключение завершает систему тем и делает ее удобной для пользователя.
Must-know факты (часто спрашивают и часто путают)
- CSS-переменные вычисляются в runtime и могут переопределяться по каскаду.
var(--token, fallback)защищает компонент от отсутствующих токенов.- Глобальные и локальные токены должны быть согласованы по именованию.
- Тема должна менять токены, а не дублировать стили всех компонентов.
prefers-color-schemeполезен даже при ручном переключателе темы.- Нельзя оставлять в компоненте "жесткие" цвета, если проект работает на токенах.
- Для сложных систем нужны semantic-токены, а не только raw-цвета.
- Смена темы должна тестироваться на контраст и читаемость.
✅ Вывод:
Понимание этих пунктов отличает "просто CSS" от зрелой темы на продакшене.
Сравнение подходов
| Подход | Что меняется | Плюсы | Риск |
|---|---|---|---|
| Жесткие значения в каждом компоненте | Каждый селектор отдельно | Быстрый старт на маленьком экране | Быстрое накопление хаоса |
Глобальные токены в :root | Единые переменные | Централизованная поддержка | Нужна дисциплина именования |
| Локальные переопределения токенов | Контекст компонента | Гибкие вариации | Легко запутаться без правил |
Тема через data-theme | Набор токенов | Масштабируемая смена тем | Нужна проверка контраста и UX |
Часто спрашивают на собеседованиях
- В чем ключевое отличие CSS-переменных от препроцессорных переменных?
- Когда использовать глобальный токен, а когда локальный?
- Зачем нужен fallback в
var()? - Как правильно строить светлую/темную тему через токены?
- Что выбрать:
prefers-color-schemeили ручной переключатель? - Какие naming-правила токенов работают в больших командах?
- Какие ошибки чаще всего ломают тему на продакшене?
✅ Вывод:
На интервью обычно проверяют архитектурное мышление, а не только синтаксис
var().Типичные ошибки
Ошибка 1: Дублирование raw-значений
❌ Неправильно:
Писать один и тот же hex-цвет в десятках мест.
✅ Правильно:
Вынести значение в токен и использовать через
var().Почему:
Иначе смена темы и рефакторинг становятся дорогими.
Ошибка 2: Нет fallback в shared-компоненте
❌ Неправильно:
Полагаться на токен, который может отсутствовать.
✅ Правильно:
Добавлять резервное значение во втором аргументе
var().Почему:
Это защищает компонент от визуальной поломки вне основного проекта.
Ошибка 3: Тема реализована копипастой стилей
❌ Неправильно:
Создавать отдельный набор дублирующих правил под dark mode.
✅ Правильно:
Переопределять только токены темы.
Почему:
Код остается компактным и проще поддерживается.
Ошибка 4: Плохое именование токенов
❌ Неправильно:
Использовать непонятные или смешанные названия (
--blue1, --text2).✅ Правильно:
Использовать semantic-имена по роли (
--color-text-primary).Почему:
Иначе команда теряет скорость и ошибается при масштабировании.
Ошибка 5: Игнорирование prefers-color-scheme
❌ Неправильно:
Не учитывать системные настройки пользователя.
✅ Правильно:
Добавить media-query или синхронизацию с ручным переключателем.
Почему:
Пользователь ожидает согласованное поведение интерфейса.
Ошибка 6: Смешение токенов и "магических" чисел
❌ Неправильно:
Часть отступов через токены, часть через случайные px.
✅ Правильно:
Строить ритм от базовых токенов и
calc().Почему:
Только так поддерживается единая система пространства.
Best Practices
- Введите минимальный набор core-токенов и фиксируйте его в документации.
- Держите semantic-токены между компонентом и raw-палитрой.
- Проверяйте контраст в обеих темах как часть Definition of Done.
- Для компонентной библиотеки используйте fallback в критичных токенах.
- Разделяйте ответственность: CSS-токены за стили, JS за переключение режима.
- Не добавляйте новый токен, пока не уверены, что он решает повторяющуюся задачу.
Заключение
CSS-переменные и темы это базовый уровень зрелого фронтенда.
Они дают контроль, ускоряют изменения и позволяют развивать интерфейс без постоянного "перекраивания" всей кодовой базы.
Они дают контроль, ускоряют изменения и позволяют развивать интерфейс без постоянного "перекраивания" всей кодовой базы.
Ключевые мысли
- Переменные это система управления стилями, а не просто синтаксис.
- Тема должна строиться на переопределении токенов.
- Качество темы определяется не только красотой, но и устойчивостью, доступностью и поддерживаемостью.