CSS-переменные (их ещё называют «пользовательскими свойствами») поддерживаются веб-браузерами уже почти четыре года. Я пользуюсь ими там, где они могут пригодиться. Это зависит от проекта, над которым я работаю, и от конкретных задач, которые мне приходится решать. Работать с CSS-переменными просто, они способны принести разработчику немалую пользу. Правда, часто фронтенд-программисты применяют CSS-переменные неправильно или не понимают особенностей работы с ними.

Я написал эту статью для того чтобы собрать в ней всё, что я знаю о CSS-переменных. В ходе работы над ней я надеялся узнать о них что-то новое и упорядочить то, что мне уже известно. Вы найдёте в этом материале всё, что нужно знать о CSS-переменных. Здесь имеется множество практических примеров и описаний сценариев использования CSS-переменных.
Готовы? Если так — приступим.
Введение
CSS-переменные — это значения, которые объявляют в CSS, преследуя две цели. Первая — это многократное использование таких значений. Вторая — это сокращение объёмов CSS-кода. Рассмотрим простой пример.
.section {
border: 2px solid #235ad1;
}
.section-title {
color: #235ad1;
}
.section-title::before {
content: "";
display: inline-block;
width: 20px;
height: 20px;
background-color: #235ad1;
}
В этом примере CSS-кода значение #235ad1 используется три раза. Представьте себе, что это — часть большого проекта. В нём подобные стили разбросаны по множеству CSS-файлов. Вас попросили поменять цвет #235ad1. В такой ситуации лучшее, что можно сделать, заключается в том, чтобы прибегнуть к возможностям редакторов кода по поиску и замене строковых значений.
Но при использовании CSS-переменных подобные задачи решаются гораздо проще. Давайте поговорим о том, как объявлять CSS-переменные. Перед именем переменной должно идти два дефиса. Объявим переменную в стиле для псевдокласса :root элемента <html>:
:root {
--color-primary: #235ad1;
}
.section {
border: 2px solid var(--color-primary);
}
.section-title {
color: var(--color-primary);
}
.section-title::before {
/* Другие стили */
background-color: var(--color-primary);
}
По-моему, этот фрагмент кода выглядит гораздо чище, чем предыдущий. Переменная --color-primary является глобальной, так как объявлена она в стиле для псевдокласса :root. Но CSS-переменные можно объявлять и на уровне отдельных элементов, ограничивая область их видимости в документе.
Именование переменных
Правила именования CSS-переменных не особенно сильно отличаются от правил, принятых в различных языках программирования. А именно, правильное имя CSS-переменной может включать в себя алфавитно-цифровые символы, знаки подчёркивания и дефисы. Кроме того, стоит обратить внимание на то, что имена этих переменных чувствительны к регистру.
/* Правильные имена */
:root {
--primary-color: #222;
--_primary-color: #222;
--12-primary-color: #222;
--primay-color-12: #222;
}
/* Неправильные имена */
:root {
--primary color: #222; /* Пробелы использовать нельзя */
--primary$%#%$#
}
Область видимости переменных
CSS-переменные имеют одну полезную особенность, которая заключается в том, что область их видимости можно ограничивать. В основе этой идеи лежат те же принципы, которые применяются в различных языках программирования. Например — в JavaScript:
let element = "cool";
function cool() {
let otherElement = "Not cool";
console.log(element);
}
В этом примере переменная element является глобальной, она доступна в функции cool(). Но к переменной otherElement можно обратиться только из тела функции cool(). Рассмотрим эту идею в применении к CSS-переменным.
:root {
--primary-color: #235ad1;
}
.section-title {
--primary-color: d12374;
color: var(--primary-color);
}
Переменная --primary-color является глобальной, обратиться к ней можно из любого элемента документа. Если переопределить её в блоке .section-title, это приведёт к тому, что её новым значением можно будет пользоваться только в этом блоке.
Вот схема, разъясняющая эту идею.
Область видимости CSS-переменных
Тут показана переменная --primary-color, используемая для настройки цвета заголовков разделов. Нам нужно настроить цвет заголовков разделов со сведениями об избранных авторах и о свежих статьях. Поэтому в стилях этих разделов мы переопределяем данную переменную. То же самое происходит и с переменной --unit. Вот стили, на которых основана предыдущая схема.
/* Глобальные переменные */
:root {
--primary-color: #235ad1;
--unit: 1rem;
}
/* Для цвета и отступа применяются глобальные значения */
.section-title {
color: var(--primary-color);
margin-bottom: var(--unit);
}
/* Переопределение переменной, управляющей цветом */
.featured-authors .section-title {
--primary-color: #d16823;
}
/* Переопределение переменных, управляющими цветом и отступом */
.latest-articles .section-title {
--primary-color: #d12374;
--unit: 2rem;
}
Использование резервных значений
Обычно «резервные значения» используются для обеспечения работоспособности сайтов в браузерах, которые не поддерживают некие современные CSS-механизмы. Но здесь речь пойдёт не об этом, а о том, как задавать значения, используемые в том случае, если нужные CSS-переменные оказываются недоступными. Рассмотрим следующий пример:
.section-title {
color: var(--primary-color, #222);
}
Обратите внимание на то, что функции var() передано несколько значений. Второе из них, #222, будет использовано только в том случае, если переменная --primary-color не будет определена. При указании резервных значений можно использовать и вложенные конструкции var():
.section-title {
color: var(--primary-color, var(--black, #222));
}
Этот подход к работе с переменными может оказаться полезным в том случае, если значение переменной зависит от некоего действия. Если может случиться так, что в переменной не будет значения, важно предусмотреть использование резервного значения.
Примеры и сценарии использования CSS-переменных
Управление размером компонентов

В дизайн-системах часто имеются, например, кнопки разных размеров. Как правило, речь идёт о трёх вариантах размеров (маленький, обычный, большой). С использованием CSS-переменных можно очень легко описывать такие кнопки и другие подобные элементы.
.button {
--unit: 1rem;
padding: var(--unit);
}
.button--small {
--unit: 0.5rem;
}
.button--large {
--unit: 1.5rem;
}
Меняя значение переменной --unit в области видимости, соответствующей компоненту кнопки, мы создаём разные варианты кнопки.
CSS-переменные и HSL-цвета
HSL (Hue, Saturation, Lightness — тон, насыщенность, светлота) — это цветовая модель, в которой компонент H определяет цвет, а компоненты S и L определяют параметры насыщенности и светлоты цвета.
:root {
--primary-h: 221;
--primary-s: 71%;
--primary-b: 48%;
}
.button {
background-color: hsl(var(--primary-h), var(--primary-s), var(--primary-b));
transition: background-color 0.3s ease-out;
}
/* Затемнение фона */
.button:hover {
--primary-b: 33%;
}
Обратите внимание на то, как я сделал цвет кнопки темнее, уменьшив значение переменной --primary-b.
Изменение размеров элементов с сохранением пропорций
Если вы работали в какой-нибудь дизайнерской программе, вроде Photoshop, Sketch, Figma или Adobe XD, то вы можете знать об использовании клавиши Shift при изменении размеров объектов. Благодаря этому приёму можно избежать искажения пропорций элементов.
В CSS нет стандартного механизма изменения размеров элементов с сохранением пропорций. Но это ограничение можно обойти, воспользовавшись, как несложно догадаться, CSS-переменными.

Предположим, что у нас имеется значок, ширина и высота которого должны быть одинаковыми. Для того чтобы этого добиться, я определил CSS-переменную --size, использовав её для настройки ширины и высоты элемента.
.icon {
--size: 22px;
width: var(--size);
height: var(--size);
}
В результате оказывается, что этот приём имитирует использование клавиши Shift при изменении размеров объектов. Тут достаточно изменить значение одной переменной --size. Подробнее эта тема освещена здесь.
Макеты, основанные на CSS Grid
CSS-переменные могут оказаться чрезвычайно полезными при проектировании макетов страниц, основанных на CSS Grid. Представьте, что вам нужно сделать так, чтобы Grid-контейнер выводил бы дочерние элементы, основываясь на заранее заданной ширине элементов. Вместо того чтобы создавать класс для каждого варианта представления элементов, что приведёт к необходимости дублирования CSS-кода, эту задачу можно решить, воспользовавшись CSS-переменными.
.wrapper {
--item-width: 300px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--item-width), 1fr));
grid-gap: 1rem;
}
.wrapper-2 {
--item-width: 500px;
}
Благодаря такому подходу можно создать гибкий Grid-макет, подходящий для использования в различных проектах, который легко поддерживать. Ту же идею можно применить и для настройки свойства grid-gap.
.wrapper {
--item-width: 300px;
--gap: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--item-width), 1fr));
}
.wrapper.gap-1 {
--gap: 16px;
}
Хранение в переменных значений со сложной структурой
CSS-градиенты
Под «значениями со сложной структурой» я понимаю, например, нечто вроде градиентов. Если в проекте есть градиент или фон, используемые во многих местах этого проекта, то их описания имеет смысл хранить в CSS-переменных.
:root {
--primary-gradient: linear-gradient(150deg, #235ad1, #23d1a8);
}
.element {
background-image: var(--primary-gradient);
}
В подобных ситуациях в переменных можно хранить и отдельные элементы «сложных» значений. Это, например, может быть угол градиента:
.element {
--angle: 150deg;
background-image: linear-gradient(var(--angle), #235ad1, #23d1a8);
}
.element.inverted {
--angle: -150deg;
}
Позиция фона
Как уже было сказано, CSS-переменные могут хранить значения со сложной структурой. Это может оказаться полезным в том случае, если имеется элемент, который, в зависимости от происходящего, может понадобиться размещать в разных местах страницы.
.table {
--size: 50px;
--pos: left center;
background: #ccc linear-gradient(#000, #000) no-repeat;
background-size: var(--size) var(--size);
background-position: var(--pos);
}
Переключение между тёмной и светлой темами
Сейчас сайты, почти в обязательном порядке, оснащают тёмной и светлой темой. Для решения этой задачи можно воспользоваться CSS-переменными, храня в них сведения о цветах и переключаясь между ними после анализа системных параметров или настроек, выполненных пользователем.
:root {
--text-color: #434343;
--border-color: #d2d2d2;
--main-bg-color: #fff;
--action-bg-color: #f9f7f7;
}
/* Класс, добавленный к элементу <html> */
.dark-mode {
--text-color: #e9e9e9;
--border-color: #434343;
--main-bg-color: #434343;
--action-bg-color: #363636;
}
Установка значений, применяемых по умолчанию
В некоторых ситуациях нужно задавать CSS-переменные с использованием JavaScript. Представим, что нам нужно установить значение свойства height элемента, размеры которого могут меняться. Я узнал о данном приёме из этой статьи.
Переменная --details-height-open изначально пуста. Её планируется использовать в описании стиля некоего элемента. Она должна содержать высоту элемента в пикселях. Если из JavaScript установить значение этой переменной по какой-то причине не удастся, важно предусмотреть применение вместо неё некоего резервного значения, используемого по умолчанию.
.section.is-active {
max-height: var(--details-height-open, auto);
}
В этом примере роль значения, используемого по умолчанию, играет auto. Оно будет применено в том случае, если из JavaScript не удастся задать значение переменной --details-height-open.
Регулирование ширины элемента-контейнера

Элементы-контейнеры, используемые на веб-страницах, могут, в разных ситуациях, иметь разные размеры. Возможно, на одной странице может понадобиться небольшой контейнер, а на другой — контейнер побольше. В подобных случаях CSS-переменные могут с успехом использоваться для управления размерами контейнеров.
.wrapper {
--size: 1140px;
max-width: var(--size);
}
.wrapper--small {
--size: 800px;
}
Встроенные стили
Использование CSS-переменных во встроенных стилях может открыть перед фронтенд-разработчиком массу новых возможностей, о которых он раньше и не подозревал. На самом деле, я написал об этом целую статью, но тут я, всё же, расскажу о самых интересных способах использования переменных во встроенных стилях.
Возможно, в продакшне этими методами лучше не пользоваться. Они очень хорошо подходят для прототипирования и для исследования различных дизайнерских идей.
Динамические Grid-элементы
Например, для настройки ширины элемента можно воспользоваться переменной --item-width, объявленной прямо в атрибуте элемента style. Такой подход может оказаться полезным при прототипировании Grid-макетов.
Вот HTML-код элемента:
<div class="wrapper" style="--item-width: 250px;">
<div></div>
<div></div>
<div></div>
</div>
Вот стиль, применяемый к этому элементу:
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--item-width), 1fr));
grid-gap: 1rem;
}
Здесь можно поэкспериментировать с примером к этому разделу.
Аватары пользователей

Ещё один интересный вариант использования CSS-переменных во встроенных стилях представлен созданием элементов разных размеров. Предположим, нам, в разных ситуациях, нужно выводить аватар пользователя различных размеров. При этом мы хотим управлять его размером с использованием единственной CSS-переменной.
Вот разметка:
<img src="user.jpg" alt="" class="c-avatar" style="--size: 1" />
<img src="user.jpg" alt="" class="c-avatar" style="--size: 2" />
<img src="user.jpg" alt="" class="c-avatar" style="--size: 3" />
<img src="user.jpg" alt="" class="c-avatar" style="--size: 4" />
Вот стили:
.c-avatar {
display: inline-block;
width: calc(var(--size, 1) * 30px);
height: calc(var(--size, 1) * 30px);
}
Проанализируем эти стили:
У нас имеется конструкция var(--size, 1). В ней предусмотрено значение, применяемое по умолчанию. Оно используется в том случае, если значение переменной --size не задано с использованием атрибута style стилизуемого элемента.
Минимальный размер элемента устанавливается равным 30px*30px.
Медиазапросы
Совместное использование CSS-переменных и медиазапросов может принести немалую пользу в деле настройки значений переменных, используемых на всех страницах веб-сайтов. Самый простой пример использования этой техники, который приходит мне в голову, заключается в настройке расстояния между элементами:
:root {
--gutter: 8px;
}
@media (min-width: 800px) {
:root {
--gutter: 16px;
}
}
В результате свойства любого элемент, в котором используется переменная --gutter, будут зависеть от ширины области просмотра браузера. Как по мне, так это — просто замечательная возможность.
Наследование
CSS-переменные поддерживают наследование. Если в пределах родительского элемента объявлена CSS-переменная, то элементы-потомки наследуют эту переменную. Рассмотрим пример.
Вот HTML-код:
<div class="parent">
<p class="child"></p>
</div>
Вот стили:
.parent {
--size: 20px;
}
.child {
font-size: var(--size);
}
Элемент .child наследует переменную --size, объявленную в стиле элемента .parent. У элемента .child есть доступ к этой переменной. Полагаю, это — очень интересно. Возможно, вы сейчас задаётесь вопросом о том, какая нам от этого польза. Полагаю, ответить на этот вопрос поможет следующий пример из жизни.

Тут имеется группа кнопок, к которым предъявляются следующие требования:
- Возможность изменения размера всех элементов путём установки значения единственной переменной.
- Расстояние между элементами должно динамически меняться в зависимости от их размера. При увеличении элементов расстояние между ними увеличивается, а при уменьшении — уменьшается.
Вот разметка к этому примеру:
<div class="actions">
<div class="actions__item"></div>
<div class="actions__item"></div>
<div class="actions__item"></div>
</div>
Вот стили:
.actions {
--size: 50px;
display: flex;
gap: calc(var(--size) / 5);
}
.actions--m {
--size: 70px;
}
.actions__item {
width: var(--size);
height: var(--size);
}
Обратите внимание на то, как я использовал переменную --size при настройке свойства gap Flexbox-элементов. Это позволяет, основываясь на переменной --size, динамически менять расстояние между элементами
Ещё один пример использования механизма наследования CSS-переменных представлен настройкой CSS-анимаций. Этот пример я взял отсюда.
@keyframes breath {
from {
transform: scale(var(--scaleStart));
}
to {
transform: scale(var(--scaleEnd));
}
}
.walk {
--scaleStart: 0.3;
--scaleEnd: 1.7;
animation: breath 2s alternate;
}
.run {
--scaleStart: 0.8;
--scaleEnd: 1.2;
animation: breath 0.5s alternate;
}
При таком подходе нам не нужно дважды объявлять @keyframes. В стилях .walk и .run переопределяются значения унаследованных переменных.
Валидация CSS-переменных
Если оказывается, что с CSS-переменной, переданной функции var(), что-то не так, браузер заменит значение этой переменной на исходное (унаследованное) значение соответствующего свойства.
:root {
--main-color: 16px;
}
.section-title {
color: var(--main-color);
}
Здесь в переменную --main-color, используемую для настройки свойства color, записано значение 16px. А это — совершенно неправильно. Свойство color наследуется. Браузер в этой ситуации работает по следующему алгоритму:
Является ли свойство наследуемым?
Вот схема работы браузера.
Концепция недопустимого значения, появляющегося во время вычислений
То, о чём шла речь выше, с технической точки зрения, называется «недопустимым значением, появляющимся во время вычислений» (Invalid At Computed-Value Time). Ситуация, в которой появляются такие значения, возникает, когда функции var() передаётся корректная CSS-переменная, значение которой не подходит для записи в настраиваемое с её помощью свойство.
Рассмотрим следующий пример, который я взял из этой статьи:
.section-title {
top: 10px;
top: clamp(5px, var(--offset), 20px);
}
Если браузер не поддерживает функцию clamp() — воспользуется ли он, в качестве резервного, значением, заданным в конструкции top: 10px? Если кратко ответить на этот вопрос, то нет — не воспользуется. Причина этого заключается в том, что к тому моменту, когда браузер обнаружит некорректное значение, которое пытаются записать в свойство, он уже отбросит, в соответствии с порядком каскадного применения стилей, другие значения. То есть — он просто проигнорирует конструкцию top: 10px.
Вот что говорится об этом в спецификации CSS:
Концепция появления недопустимого значения во время вычислений существует из-за того, что ошибки, связанные с переменными, не проявляются, в отличие от других синтаксических ошибок, на ранних стадиях работы системы. Поэтому оказывается, что когда пользовательский агент обнаружит, что значение переменной некорректно, он уже отбросит, в соответствии с порядком каскадного применения стилей, другие значения.
В результате оказывается, что если нужно применять возможности CSS, не пользующиеся широкой поддержкой браузеров, при реализации которых используются CSS-переменные, нужно применять директиву @supports. Вот как это сделано в вышеупомянутой статье:
@supports (top: max(1em, 1px)) {
#toc {
top: max(0em, 11rem - var(--scrolltop) * 1px);
}
}
Интересные находки
Хранение URL в переменных
Возможно, некоторые ресурсы, используемые на ваших веб-страницах, нужно загружать из внешних источников. В подобных ситуациях можно хранить URL этих ресурсов в CSS-переменных.
:root {
--main-bg: url("https://example.com/cool-image.jpg");
}
.section {
background: var(--main-bg);
}
Тут может возникнуть вопрос о том, можно ли обрабатывать конструкции вида var(--main-bg) с помощью CSS-функции url(). Рассмотрим следующий пример:
:root {
--main-bg: "https://example.com/cool-image.jpg";
}
.section {
background: url(var(--main-bg));
}
Так поступить не получится, так как функция url() воспринимает всю конструкцию var(--main-bg) в виде URL, а это неправильно. К тому моменту, когда браузер вычислит значение, оно уже будет некорректным, рассмотренная конструкция не будет работать так, как ожидается.
Хранение нескольких значений
В CSS-переменных можно хранить несколько значений. Если это — значения, выглядящие так, как они должны выглядеть в том месте, где планируется использовать переменную, то такая конструкция окажется работоспособной. Рассмотрим пример.

Вот CSS-код:
:root {
--main-color: 35, 90, 209;
}
.section-title {
color: rgba(var(--main-color), 0.75);
}
Здесь имеется функция rgba() и RGB-значения, разделённые запятой и записанные в CSS-переменную. Эти значения используются при задании цвета. При таком подходе к использованию функции rgba() у разработчика появляется возможность воздействовать на значение, соответствующее альфа-каналу цвета, настраивая цвет различных элементов.
Единственный минус такого подхода заключается в том, что цвет, задаваемый функцией rgba(), нельзя будет настраивать, пользуясь инструментами разработчика браузера. Если при работе над вашим проектом эта возможность важна — вам, вероятно, не подойдёт вышеописанный способ использования функции rgba().
Вот пример использования CSS-переменной для настройки свойства background:
:root {
--bg: linear-gradient(#000, #000) center/50px;
}
.section {
background: var(--bg);
}
.section--unique {
background: var(--bg) no-repeat;
}
Здесь показана стилизация двух разделов сайта. Фон одного из них не должен повторяться по осям x и y.
Изменение значений CSS-переменных в теле правила @keyframes
Если вы читали спецификацию, посвящённую CSS-переменным, то вы могли столкнуться там с термином «animation-tainted». С его помощью описывают тот факт, что значения CSS-переменных не поддаются плавному изменению в правиле @keyframes. Рассмотрим пример.
Вот HTML-код:
<div class="box"></div>
Вот стили:
.box {
width: 50px;
height: 50px;
background: #222;
--offset: 0;
transform: translateX(var(--offset));
animation: moveBox 1s infinite alternate;
}
@keyframes moveBox {
0% {
--offset: 0;
}
50% {
--offset: 50px;
}
100% {
--offset: 100px;
}
}
Анимация в этом случае не будет плавной. Переменная примет лишь три значения: 0, 50px и 100px. В спецификации CSS говорится, что любое пользовательское свойство, использованное в правиле @keyframes, становится animation-tainted-свойством, что влияет на то, как оно обрабатывается посредством функции var() при анимировании элементов.
Если нам нужно обеспечить в предыдущем примере плавную анимацию, то делать это надо так, как делалось раньше. То есть — надо заменить переменную на те CSS-свойства элемента, которые нужно анимировать.
@keyframes moveBox {
0% {
transform: translateX(0);
}
50% {
transform: translateX(50px);
}
100% {
transform: translateX(100px);
}
}
Хочу отметить, что после публикации этой статьи мне сообщили о том, что анимировать CSS-переменные в @keyframes, всё же, можно. Но для этого переменные надо регистрировать с использованием правила @property. Пока эта возможность поддерживается лишь браузерами, основанными на Chromium.
@property --offset {
syntax: "<length-percentage>";
inherits: true;
initial-value: 0px;
}
Вычисления
Возможно, вы не знаете о том, что CSS-переменные можно использовать в вычислениях. Взглянем на пример, который мы уже рассматривали, говоря об аватарах:
.c-avatar {
display: inline-block;
width: calc(var(--size, 1) * 30px);
height: calc(var(--size, 1) * 30px);
}
Размеры аватара зависят от значения переменной --size. Значением, используемым по умолчанию, является 1. Это значит, что стандартным размером аватара является 30px*30px. Обратите внимание на следующие стили и на то, что изменение этой переменной приводит к изменению размера аватара.
.c-avatar--small {
--size: 2;
}
.c-avatar--medium {
--size: 3;
}
.c-avatar--large {
--size: 4;
}
Итоги
Я рассказал довольно много всего о CSS-переменных. Надеюсь, вам пригодится то, что вы сегодня узнали.