Что такое CSS-переменные — и почему они живут прямо в браузере

Если ты слышал про переменные в Sass или Less, у тебя уже есть картинка в голове: «имя вместо значения, чтобы не повторяться». CSS-переменные делают то же самое — но есть разница, из-за которой они умеют то, что препроцессорным и не снилось.
Переменные Sass исчезают на этапе сборки: в браузер приезжает готовый #141416. CSS-переменные доезжают до браузера живыми. Они каскадируются, наследуются и меняются на лету. Именно поэтому тёмная тема переключается одной строчкой, а не пересборкой стилей.
Как это выглядит
Объявляешь свойство, имя которого начинается с двух дефисов. Читаешь через функцию var().
:root {
--brand: #d4a373;
--gap: 12px;
}
.button {
background: var(--brand);
padding: var(--gap);
}
:root — это просто корневой элемент страницы. Объявил там — видно везде ниже. Официально штука называется не «переменная», а custom property, «пользовательское свойство», и это не придирка к словам: она ведёт себя как настоящее CSS-свойство, со всеми вытекающими.
У var() есть второй аргумент — запасное значение: var(--brand, #d4a373). Если переменная не объявлена, возьмётся запасное.
Главное: они наследуются
Вот что делает их по-настоящему полезными. Переменная, объявленная через два дефиса, наследуется от родителя и подчиняется каскаду — как color или font-size.
Значит, её можно переопределить точечно:
:root { --bg: #141416; }
.card--light { --bg: #f4efe7; }
Всё, что лежит внутри .card--light, увидит новое значение --bg, а остальная страница — старое. Ты не трогал ни одно правило, где используется var(--bg): ты поменял значение в одной ветке дерева, и оно потекло вниз.
Отсюда и тёмная тема: объявляешь набор цветов в :root, а рядом — тот же набор с другими значениями внутри [data-theme="dark"]. Переключение темы — это смена одного атрибута на html. Как собрать это целиком, разобрано в гайде как добавить тёмную тему.
Три подвоха, на которых спотыкаются все
Регистр важен. --my-color и --My-color — это две разные переменные. В остальном CSS регистр свойств не важен, поэтому ловушка обидная: опечатка не подсветится, просто ничего не применится.
Они не работают внутри медиазапросов. Написать @media (min-width: var(--bp)) нельзя — MDN говорит об этом прямо. Переменную можно подставить только в значение свойства, но не в условие медиазапроса, не в селектор и не в имя свойства.
Сломанная переменная не откатывается к предыдущему значению. Вот самый неочевидный. Обычно, если в CSS написана ерунда, браузер выбрасывает эту строку и берёт предыдущую по каскаду. С var() так не выйдет: к моменту, когда браузер понимает, что подставленное значение бессмысленно, остальные варианты он уже отбросил. Свойство получает unset — то есть наследует значение родителя или откатывается к начальному.
Практический вывод: color: var(--accent) с пустой --accent даст не «цвет по умолчанию из твоих стилей», а унаследованный цвет родителя. Выглядит как мистика — на деле просто как задокументировано. Лечится запасным значением: var(--accent, #d4a373).
Где объявлять: в :root или в компоненте
Частый вопрос: складывать всё в :root или раскидывать по компонентам? Рабочее разделение такое:
- В
:root— то, что описывает продукт целиком: палитра, шаг сетки, радиусы, шрифты. Единая точка правды, меняешь один раз. - В компоненте — то, что имеет смысл только внутри него:
--card-padding,--avatar-size. Заодно это даёт компоненту «ручки»: снаружи можно переопределить одну переменную, не переписывая его стили.
И важное уточнение ко второму подвоху. Внутри медиазапроса переменную объявлять можно — нельзя только подставлять её в условие. Вот так всё работает:
:root { --gap: 12px; }
@media (min-width: 900px) {
:root { --gap: 24px; }
}
Все отступы, завязанные на var(--gap), разом вырастут на широком экране. Один медиазапрос вместо десятка правил — пожалуй, самый недооценённый приём с переменными.
Менять из JavaScript
Самое вкусное: переменную можно записать из кода.
document.documentElement.style.setProperty('--brand', '#62bd98');
Одна строка — и перекрашивается всё, что на неё завязано. Прочитать текущее значение можно через getComputedStyle(el).getPropertyValue('--brand').
Так делают переключатели тем, слайдеры настроек и живые превью. Кстати, именно этим приёмом обходят главное ограничение вариативных шрифтов: значение оси кладут в переменную и меняют только её.
Чем CSS-переменные отличаются от переменных в Sass?
Переменные Sass существуют только до компиляции — в браузер приезжают готовые значения, поменять их в рантайме нельзя. CSS-переменные живут в браузере: каскадируются, наследуются, доступны из JavaScript и из инструментов разработчика.
Почему запасное значение не спасло?
Проверь, действительно ли переменная не определена. Если она определена, но со сломанным значением, var() подставит именно его — и свойство станет unset. Запасное значение помогает только там, где переменной нет совсем.
Что такое @property?
Способ объявить переменную «с типом»: указать, что это цвет или длина, задать начальное значение и запретить наследование. Типизированные переменные умеют анимироваться — обычные при переходе просто перескакивают. Если же переменные вроде прописаны, а в браузере всё по-старому, сначала проверь базовые причины: почему CSS не применяется. А если весь проект на утилитарных классах, посмотри, как это соотносится с Tailwind против чистого CSS — там переменные лежат в основе тем.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





