Что такое…

Что такое 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 — там переменные лежат в основе тем.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →