CSS переменные: полное руководство от основ до продвинутых техник

Мир фронтенда постоянно развивается, и одна из его самых значимых эволюций — появление нативных CSS переменных, официально известных как Custom Properties. Если вы до сих пор считаете их просто аналогом переменных из Sass или Less, эта статья перевернет ваше представление. CSS переменные — это не просто удобство для хранения цвета или размера шрифта. Это мощный инструмент, который меняет парадигму взаимодействия CSS, HTML и JavaScript, предлагая настоящую динамику, каскадирование и реактивность прямо в таблицах стилей.

Эта статья проведет вас от основ до продвинутых техник, покажет практические кейсы и убедит, что будущее стилизации веб-приложений уже здесь.

Основы. Чем CSS переменные принципиально отличаются от переменных препроцессоров

Давайте начнем с азов. В препроцессорах, как Sass, переменная — это абстракция, которая существует только на этапе разработки. Препроцессор подставляет её значение в конечный CSS-файл. После компиляции переменная исчезает.

Sass-переменная:

$primary-color: #3498db;
.button { background-color: $primary-color; }

После компиляции в CSS мы увидим: .button { background-color: #3498db; }. Значение зафиксировано.

CSS переменная — это полноценное CSS-свойство. Оно существует в браузере, участвует в каскаде, наследовании и может быть изменено на лету.

:root {
  --primary-color: #3498db; /* Объявляем переменную */
}
.button {
  background-color: var(--primary-color); /* Используем через функцию var() */
}

Ключевые отличия уже видны:

  1. Имена начинаются с двух дефисов: --my-variable.
  2. Область видимости: Они привязаны к элементу и его потомкам (наследуются), если не переопределены.
  3. Доступность в браузере: Их можно читать и изменять с помощью JavaScript.

Как объявлять и использовать

Объявление происходит как у обычного свойства. Лучшее место для глобальных переменных — селектор :root (корень документа, обычно <html>).

:root {
  --main-color: #e74c3c;
  --spacing-unit: 16px;
  --header-height: 60px;
}

Использовать значение переменной можно с помощью функции var().

.header {
  height: var(--header-height);
  background-color: var(--main-color);
  margin-bottom: var(--spacing-unit);
}

Запасное значение (Fallback)

Функция var() принимает второй, необязательный аргумент — запасное значение, которое будет использовано, если переменная не определена или невалидна.

.element {
  /* Если --accent-color нет, будет использован синий цвет */
  color: var(--accent-color, blue);
  /* Можно использовать и другую переменную как fallback */
  padding: var(--custom-padding, var(--spacing-unit));
}

Это одна из ключевых фич, которая открывает возможности для создания тем и безопасного экспериментального CSS.

Уникальное поведение: каскад, наследование и циклы

Вот где начинается настоящая магия. Поскольку CSS переменные — это CSS-свойства, они подчиняются тем же правилам, что и, например, color или font-size.

Каскад и переопределение

Переменные можно переопределять для конкретных элементов, медиа-запросов или состояний. Это основа для создания контекстных стилей и тем.

:root {
  --theme-bg: white;
  --theme-text: black;
}
body.dark-mode {
  --theme-bg: #1a1a1a;
  --theme-text: #f0f0f0;
}
body {
  background-color: var(--theme-bg);
  color: var(--theme-text);
  transition: background-color 0.3s, color 0.3s;
}

Изменение класса у <body> на dark-mode мгновенно переопределит значения переменных для всего поддерева и обновит стили.

Наследование и его отмена

Как и color, CSS переменные наследуются потомками. Но иногда это не нужно. Наследование можно «оборвать», установив для переменной специальное значение initial.

.card {
  --card-highlight: gold; /* Эта переменная будет видна внутри .card */
}
.card__footer {
  --card-highlight: initial; /* Здесь мы "обрываем" наследование, значение сбросится */
  /* Теперь var(--card-highlight) вернется к значению, объявленному выше по дереву, или к дефолтному */
}

Более радикальный способ — использовать универсальный селектор для сброса наследования, хотя на практике это требуется редко.

Опасность циклических зависимостей

CSS не является императивным языком программирования. Все объявления обрабатываются одновременно. Это создает ловушку циклических зависимостей.

:root {
  --size: 10px;
}
.box {
  width: var(--size);
  --size: calc(var(--width) - 10px); /* ОШИБКА: width зависит от --size, а --size зависит от width */
}

Браузер обнаружит эту циклическую зависимость и объявит значение переменной недействительным на момент вычисления (invalid at computed-value time — IACVT). В таком случае свойство, использующее эту переменную, откатится к своему начальному значению (например, width: auto), что может привести к неожиданному «развалу» верстки.

Избегайте ситуаций, когда переменная A зависит от переменной B, которая, в свою очередь, зависит от A.

Практическое применение: темы, адаптивный дизайн и кастомизация компонентов

Создание переключаемых тем

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

:root {
  /* Светлая тема (по умолчанию) */
  --color-bg: #fff;
  --color-text: #222;
  --color-primary: #4a6fa5;
  --shadow: 0 2px 10px rgba(0,0,0,0.1);
}
[data-theme="dark"] {
  /* Тёмная тема */
  --color-bg: #222;
  --color-text: #f0f0f0;
  --color-primary: #66cc99;
  --shadow: 0 2px 10px rgba(255,255,255,0.05);
}
body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: sans-serif;
  transition: background 0.3s ease, color 0.3s ease;
}
.button {
  background: var(--color-primary);
  box-shadow: var(--shadow);
}
<body>
  <button class="button" onclick="document.body.dataset.theme='dark'">Включить тёмную тему</button>
  <!-- Остальной контент -->
</body>

Упрощение адаптивного дизайна (Responsive Design)

Часто в медиа-запросах мы меняем множество свойств: отступы, размеры шрифтов, сетки. С переменными можно менять только набор базовых «токенов».

:root {
  --gutter: 16px;
  --columns: 4;
  --font-scale: 1;
}
@media (min-width: 768px) {
  :root {
    --gutter: 24px;
    --columns: 8;
    --font-scale: 1.125;
  }
}
@media (min-width: 1200px) {
  :root {
    --gutter: 32px;
    --columns: 12;
    --font-scale: 1.25;
  }
}
.container {
  padding: 0 var(--gutter);
  display: grid;
  grid-template-columns: repeat(var(--columns), 1fr);
  gap: var(--gutter);
}
.title {
  font-size: calc(2rem * var(--font-scale));
}

Код становится декларативным и легче поддерживается. Изменение отступа на всех разрешениях теперь требует правки в одном месте.

Стилизация компонентов с «публичным API»

Это мощнейший паттерн. Вы можете создать компонент (например, кнопку), стилизация которого контролируется не внутренними сложными селекторами, а набором четко определенных CSS-переменных. Это похоже на публичный API для стилей.

CSS компонента:

.button {
  /* Публичные переменные API с безопасными значениями по умолчанию */
  --button-bg: var(--color-primary, #4a6fa5); /* Использует глобальную или запасной цвет */
  --button-text: white;
  --button-padding: 0.75em 1.5em;
  --button-radius: 4px;
  /* Внутренние стили, использующие API */
  background-color: var(--button-bg);
  color: var(--button-text);
  padding: var(--button-padding);
  border-radius: var(--button-radius);
  border: none;
  cursor: pointer;
  transition: background-color 0.2s;
}
.button:hover {
  --button-bg: var(--color-primary-dark, #3a5a85); /* Переопределяем только нужную переменную для состояния */
}

Использование в HTML: Теперь мы можем кастомизировать каждую кнопку индивидуально, не создавая новых классов и не переопределяя правила с повышенной специфичностью.

<button class="button">Обычная</button>
<button class="button" style="--button-bg: #e74c3c; --button-radius: 20px;">Особая</button>

Это дает беспрецедентную гибкость и является отличной альтернативой генерации множества модификатор-классов в методологиях типа БЭМ.

Интеграция с JavaScript: динамика и интерактивность

Это, пожалуй, самый революционный аспект. Раньше для динамического изменения стиля из JavaScript приходилось либо менять классы, либо напрямую лезть в element.style. Теперь мы можем управлять дизайном через изменение данных (переменных).

Простой пример: отслеживание курсора мыши

Допустим, мы хотим, чтобы градиент или тень следовали за курсором.

JavaScript:

document.addEventListener('mousemove', (e) => {
  // Вычисляем положение курсора в процентах
  const x = (e.clientX / window.innerWidth) * 100;
  const y = (e.clientY / window.innerHeight) * 100;
  // Устанавливаем переменные в корень документа
  document.documentElement.style.setProperty('--mouse-x', `${x}%`);
  document.documentElement.style.setProperty('--mouse-y', `${y}%`);
});

CSS:

.follow-cursor {
  width: 300px;
  height: 300px;
  /* Центр радиального градиента привязан к положению мыши */
  background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), red, blue);
}

Перемещайте курсор мыши, чтобы увидеть как градиентное пятно следует за ним!

See the Pen mouse cursor tracking by inteltone (@inteltone) on CodePen.

Связь чистая и простая: JS занимается данными (—mouse-x), CSS — их визуальным представлением.

Создание кастомного ползунка (Range Input)

Значение ползунка можно напрямую «прокидывать» в CSS.

HTML:

<input type="range" min="0" max="100" value="50" class="custom-slider">
<div class="slider-preview"></div>

JS:

const slider = document.querySelector('.custom-slider');
const root = document.documentElement;
slider.addEventListener('input', (e) => {
  root.style.setProperty('--slider-value', e.target.value + '%');
});

CSS:

.slider-preview {
  width: 300px;
  height: 30px;
  /* Ширина фона зависит от значения ползунка */
  background: linear-gradient(90deg, #3498db 0%, #3498db var(--slider-value, 50%), #eee var(--slider-value, 50%));
  border: 1px solid #ccc;
}

Перемещайте ползунок слайдера, чтобы увидеть как на это реагирует связанный с ним элемент!

See the Pen Creating a Custom Slider (Range Input) by inteltone (@inteltone) on CodePen.

Индикатор прогресса прокрутки (Scroll Progress)

Аналогично можно создать индикатор, показывающий, какую часть страницы или контейнера пользователь уже проскроллил.

JavaScript:

const scrollableContainer = document.querySelector('.scrollable-content');
scrollableContainer.addEventListener('scroll', () => {
  const scrollPercent = (scrollableContainer.scrollTop / (scrollableContainer.scrollHeight - scrollableContainer.clientHeight)) * 100;
  scrollableContainer.style.setProperty('--scroll-progress', `${scrollPercent}%`);
});

CSS:

.scrollable-content {
  --scroll-progress: 0%;
  height: 300px;
  overflow-y: auto;  
}
.scrollable-content::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  height: 4px;
  width: var(--scroll-progress);
  background: linear-gradient(90deg, red, orange);
  z-index: 10;
}

Воспользуйтесь прокруткой страницы, чтобы увидеть Индикатор прокрутки страницы в действии!

See the Pen Scroll Progress Indicator by inteltone (@inteltone) on CodePen.

Продвинутые техники и «хаки»

Имитация свойств-миксинов (Mixins) и сокращений (Shorthands)

Вы можете создать «псевдосвойство», которое предзаполняет часть значений реального свойства.

/* Создаем свой "миксин" для тени с фиксированным цветом */
.element {
  --purple-shadow: 0 10px 20px rebeccapurple; /* Предопределяем всю тень */
  box-shadow: var(--purple-shadow, 0 10px 20px #000); /* Используем с fallback */
}
/* Теперь мы можем переопределять только часть, например, размытие */
.element:hover {
  --purple-shadow: 0 10px 30px rebeccapurple; /* Меняем только размытие с 20px на 30px */
}

Работа с SVG

CSS-переменные прекрасно работают внутри SVG, позволяя динамически управлять атрибутами.

<svg width="200" height="200">
  <circle cx="100" cy="100" r="50" class="morphing-circle"/>
</svg>
<button onclick="changeColor()">Сменить цвет</button>
.morphing-circle {
  fill: var(--circle-fill, #3498db); /* Управляем заливкой через CSS */
  transition: fill 0.3s;
}
function changeColor() {
  document.querySelector('.morphing-circle').style.setProperty('--circle-fill', '#e74c3c');
}

Кликните на кнопке «Сменить цвет», чтобы увидеть изменение цвета круга.

See the Pen Working with SVG by inteltone (@inteltone) on CodePen.

Полифилы и поддержка браузеров

На сегодняшний день поддержка CSS переменных составляет более 95% глобально. Для старых браузеров (в основном IE11) существуют полифилы, например, css-vars-ponyfill. Однако чаще используется подход постепенной улучшаемости (progressive enhancement):

.button {
  background: #4a6fa5; /* Фолбэк для старых браузеров */
  background: var(--primary-color, #4a6fa5); /* Современные браузеры используют это */
}

Или с помощью @supports:

.button {
  background: #4a6fa5;
}
@supports (color: var(--test)) {
  .button {
    background: var(--primary-color);
  }
}

Ограничения и подводные камни

  1. Нельзя использовать в именах свойств, селекторах или медиа-запросах. var(--size) работает только в значении свойства. margin-top: var(--size); — да. var(--prop): 10px; — нет.
  2. Проблема с конкатенацией строк. Нельзя сделать так: url('img/' var(--image-name)). Решение — хранить полный путь в переменной: url(var(--full-image-path)).
  3. Чувствительность к регистру и пробелам. --color и --Color — разные переменные. Значение --value: ; (пробел) является валидным и будет передано в функцию var().

Заключение. Почему это важно для вашего бизнеса

CSS переменные — это не очередной модный фреймворк, а фундаментальное улучшение языка стилей. Они позволяют создавать по-настоящему динамические, гибкие и легко поддерживаемые интерфейсы. Разделение ответственности становится четче: JavaScript управляет данными и состоянием, а CSS — их визуальным воплощением. Это приводит к более чистому коду, ускорению разработки и снижению стоимости поддержки проектов.

Готовы вывести ваш цифровой продукт на новый уровень?

Команда нашей веб-студии находится на передовой современных технологий, таких как CSS переменные. Мы не просто создаем статичные макеты — мы проектируем живые, адаптивные и интерактивные интерфейсы, которые работают на вас, повышая вовлеченность и конверсию.

Хотите сайт, который впечатляет не только дизайном, но и техническим совершенством? Давайте обсудим ваш проект. Мы поможем воплотить самые смелые идеи, используя лучшие практики современной фронтенд-разработки, чтобы ваш ресурс был быстрым, гибким и готовым к любым вызовам цифрового мира. Свяжитесь с нами для консультации — создадим что-то выдающееся вместе.

Теги: