CSS-переменные (custom properties)
Урок про нативные переменные CSS: как объявить, прочитать, унаследовать и менять их прямо в браузере без сборки.
CSS-переменная (custom property) — это объявление вида
--имя: значение, которое живёт в дереве элементов, наследуется потомками и читается функциейvar(--имя)прямо во время работы страницы.
Зачем это на практике
Раньше, чтобы поменять основной цвет темы, приходилось искать его десятки раз по всему файлу или гонять препроцессор. CSS-переменные убирают эту боль: вы объявляете значение один раз, а используете его сколько угодно. Когда дизайнер просит «сделать акцент чуть темнее», вы правите одну строку. Но главная сила в другом — в отличие от переменных Sass, нативные переменные живут в браузере и их можно читать и менять во время работы страницы: по клику, по медиа-запросу, из JavaScript. Именно на них держатся современные системы тем (светлая/тёмная) и дизайн-токены.
Объявление и чтение
Переменную объявляют как обычное свойство, но имя начинается с двух дефисов. Чаще всего её кладут в псевдокласс :root — это корень документа (элемент html), и оттуда значение видно всей странице.
:root {
--brand: #2563eb;
--space: 16px;
--radius: 8px;
}
.button {
background: var(--brand);
padding: var(--space);
border-radius: var(--radius);
}
Функция var(--brand) подставляет текущее значение переменной. Имена чувствительны к регистру: --brand и --Brand — разные переменные. Значением может быть что угодно: цвет, число с единицей, целый список (--shadow: 0 1px 3px rgba(0,0,0,0.2)) и даже кусок значения.
Наследование и каскад
Главное отличие от препроцессорных переменных: custom properties участвуют в наследовании. Объявленная на элементе переменная видна ему и всем его потомкам, но не родителям и не соседям. Это позволяет переопределять значение точечно — для отдельной ветки дерева.
:root { --text: #1f2937; }
/* Внутри тёмной карточки переопределяем только здесь */
.card--dark {
--text: #f9fafb;
background: #111827;
}
p { color: var(--text); }
Абзац внутри .card--dark получит светлый текст, а такой же абзац снаружи — тёмный. Никаких дополнительных селекторов вроде .card--dark p не понадобилось: переменная «спустилась» по дереву. Работает и обычный каскад — если две одинаково специфичные точки объявят --text, победит та, что ниже в коде.
Запасное значение (fallback)
Вторым аргументом var() можно передать запасное значение — оно используется, если переменная не определена или её значение невалидно для этого свойства.
.box {
/* если --gap нигде не задана — возьмём 12px */
gap: var(--gap, 12px);
/* fallback может быть другой переменной */
color: var(--accent, var(--brand, black));
}
Это спасает компоненты, которые могут попасть в проект без нужных токенов: вместо «свойство просто не применится» вы получаете осмысленное значение по умолчанию.
Темизация: светлая и тёмная
Самый частый реальный сценарий. Собираем палитру в переменные, а в тёмной теме переопределяем те же имена. Компонентам всё равно — они читают var(--bg) и var(--fg).
:root {
--bg: #ffffff;
--fg: #111827;
--card: #f3f4f6;
}
/* Тема по выбору пользователя */
[data-theme="dark"] {
--bg: #0b1120;
--fg: #e5e7eb;
--card: #1f2937;
}
/* Либо по системной настройке */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #0b1120;
--fg: #e5e7eb;
--card: #1f2937;
}
}
body { background: var(--bg); color: var(--fg); }
.card { background: var(--card); }
Переключение темы — это смена одного атрибута на html, а не перезагрузка стилей. Все переменные пересчитываются мгновенно, и страница перекрашивается.
Изменение из JavaScript
Поскольку переменные — часть живого CSSOM, их читают и пишут через обычный API стилей. Это мост между скриптом и оформлением без хардкода цветов в JS.
/* CSS объявляет токен */
:root { --accent: #2563eb; }
.badge { background: var(--accent); }
// Читаем текущее значение
const root = document.documentElement;
const styles = getComputedStyle(root);
console.log(styles.getPropertyValue("--accent").trim());
// Задаём новое — все .badge перекрасятся
root.style.setProperty("--accent", "#dc2626");
console.log("accent updated");
Вывод:
#2563eb accent updated
Так делают ползунки настройки темы, выбор «акцентного цвета» в интерфейсе и анимации, где значение переменной плавно меняют через requestAnimationFrame.
Отличие от переменных препроцессора
Переменные Sass/Less ($brand, @brand) и нативные --brand легко спутать, но это принципиально разные вещи.
| Свойство | Sass $var | CSS --var |
| Когда вычисляется | при компиляции, до отдачи браузеру | в браузере, во время работы |
| Есть в готовом CSS | нет, заменяется на значение | да, остаётся в коде |
| Наследуется по дереву | нет (область видимости — текст) | да (область видимости — DOM) |
| Меняется из JS / по теме | нельзя | можно |
| Видна в DevTools | нет | да |
Вывод простой: Sass-переменные хороши для констант сборки (брейкпоинты в миксинах, цвета палитры на этапе генерации), а нативные — для всего, что должно меняться в рантайме. Их часто используют вместе.
Как это работает под капотом
Custom property — это обычное наследуемое свойство, значение которого браузер хранит как «токены» почти без проверки. Валидность проверяется только в момент подстановки через var(): вот тогда движок берёт вычисленное значение из ближайшего предка, где оно объявлено, и пытается применить к конкретному свойству. Если результат бессмыслен (например, в color попало 16px), свойство получает значение unset и в дело идёт наследование или начальное значение — а не fallback из var() (он срабатывает лишь когда сама переменная пуста). Поэтому опечатка в значении переменной иногда «молча» ломает один цвет, а не всю страницу.
Частые ошибки
- Забыть два дефиса.
var(-brand)илиvar(brand)не работают — имя обязано начинаться с--. - Ждать каскад «вверх». Переменная, объявленная на потомке, не видна родителю. Кладите общие токены в
:root. - Путать fallback и невалидное значение. Если переменная задана, но её значение не подходит свойству, второй аргумент
var()не спасёт — свойство станетunset. - Считать их Sass-переменными. В
@mediaнельзя написать@media (min-width: var(--bp))— переменные не работают в самих медиа-запросах (только в значениях свойств).
Итоги
- Объявление —
--имя: значение, чтение —var(--имя, запасное). - Переменные наследуются по DOM-дереву и подчиняются каскаду — это позволяет переопределять токены точечно.
- Темы строятся переопределением одних и тех же имён в
:rootи[data-theme]. - Значения читаются и пишутся из JavaScript через
getPropertyValue/setProperty. - В отличие от Sass-переменных, нативные живут в рантайме, видны в DevTools и меняются на лету.