Я люблю создавать компоненты везде и всегда, поэтому пользовательские CSS-свойства, также известные как CSS-переменные, являются одной из моих любимых фишек, которая позволяет писать более модульный код. При работе с ними я набил достаточно шишек, выпил литры чая и убил кучу времени. Теперь я «мастер», и хочу поделиться своим опытом.
▍ Разрешённые символы при именовании
Однажды я наткнулся примерно на следующий фрагмент кода:
:root {
--component-font-size: 20px;
}
.component {
--_component-font-size: var(--component-font-size);
font-size: var(--_component-font-size, 10px);
}
Для меня нижнее подчёркивание — это прямая отсылка к старому соглашению, которое использовалось в JS для создания «внутренних» свойств объекта. Я удивился, что в CSS так тоже можно!
Как говорится в спецификации CSS Custom Properties for Cascading Variables Module Level 1, название пользовательского свойства — это специальный CSS-идентификатор <dashed-ident>
, начинающийся с --
. Одновременно к нему применимы правила синтаксиса CSS-идентификатора <ident>
, описанного в стандарте CSS Syntax Module Level 3. По правилам мы можем использовать символы [a-zA-Z0-9]
, дефис -
, подчёркивание _
и другие ASCII-символы, если их экранировать.
Поэтому предыдущий фрагмент кода абсолютно правильный. А самое интересное, что следующий фрагмент кода тоже!
body {
--*: lightgoldenrodyellow;
background-color: var(--*);
}
Честно говоря, если бы меня спросили на собеседовании, корректный ли этот код, то я бы точно сказал нет. Предыдущий пример взорвал мне
▍ Разрешены символы не только латиницы
Однажды я потратил несколько часов, чтобы найти ошибку. Браузер не определял значение для пользовательского свойства. Для объяснения я сократил тот код до следующего:
body {
--color: red;
background-color: var(--сolor);
}
Вы думаете, цвет фона у элемента body
стал красным? Вот я также думал. А он не был красным. Я стал инспектировать код и увидел, что ошибки нет. Но по какой-то причине значение red
не применялось для пользовательского свойства --color
. После медитации над кодом я решил просто скопировать --color
и вставить его для свойства background-color
. Код заработал.
Оказывается, дело в том, что при именовании пользовательских свойств мы можем использовать как латинские символы, так и кириллические. И случайно в строке --color: red
я напечатал первую с
на английском языке, а в строке background-color: var(--сolor)
уже на русском. С точки зрения браузера это два разных пользовательских свойства, и поэтому значение не применялось.
Одно дело, когда перепутал один символ, но следующий код также будет рабочим:
body {
--цвет-фона: red;
background-color: var(--цвет-фона);
}
Фон у элемента body
будет красный. Честно, я не знаю, почему браузеры могут обрабатывать символы на русском языке. Я пытался нагуглить, почему код работает. Ничего не нашёл. Если вам известен ответ, то, пожалуйста, поделитесь в комментариях.
▍ Некорректное значение не вызывает ошибки
Мы рассмотрели нюансы при именовании пользовательских свойств, но кроме них есть ещё моменты, которые сбивают с толку при работе с ними. В качестве первого примера рассмотрим код, в котором передано некорректное значение 10px
через пользовательское свойство --not-a-background-color
для свойства background-color
. Как думаете, какое итоговое значение будет во вкладке Computed?
:root {
--not-a-background-color: 10px;
}
.container {
background-color: lightgoldenrodyellow;
background-color: var(--not-a-background-color);
}
Можно подумать, что lightgoldenrodyellow
, но нет. Правильный ответ — transparent
.
В стандарте сказано, что если при замене пользовательского свойства получается некорректное значение для свойства, то браузеры вместо него будут использовать корректное. Оно вычисляется в зависимости от свойства. Если оно наследуемое, то значение передаётся в результате наследования. Если нет, используется начальное (initial
) значение.
В предыдущем примере после замены var(--not-a-background-color)
браузеры определяют, что значение 10px
некорректное. Далее они проверяют, можно ли унаследовать значение. Свойство background-color
не наследуется, поэтому подставляется начальное значение, т. е. transparent
.
:root {
--not-a-background-color: 10px;
}
.container {
background-color: lightgoldenrodyellow;
background-color: var(--not-a-background-color); /* после замены var(--not-a-background-color) будет background-color: transparent */
}
А если бы у нас было свойство, которое можно наследовать, например font-size
, то значение было унаследованное.
:root {
--not-a-font-size: lightgoldenrodyellow;
}
body {
font-size: 10px;
}
.container {
font-size: 20px;
font-size: var(--not-a-font-size); /* после замены var(--not-a-font-size) будет font-size: 10px */
}
▍ Краткая форма свойства
При работе с пользовательскими свойствами важно помнить, как они работают внутри свойств, которые являются краткой формой для других свойств, т. е свойства padding
, margin
, border
и т. п. В качестве примера я определил свойство border
и передал только два значения:
:root {
--border-width: 2px;
--border-style: solid;
}
.container {
border: var(--border-width) var(--border-style) var(--border-color);
}
Согласно стандарту, в случае отсутствия хотя бы одного значения браузеры не могут найти все составляющие свойства. В результате они не могут применить определённые нами значения. Так произошло в моём примере. В devTools мы увидим, что значения переданы, но свойство border
не применилось.
▍ Пользовательские свойства — это не переменные
Пользовательские свойства часто сравнивают с переменными из любого языка программирования, но это не так! Для примера создам градиент с помощью следующего кода:
:root {
--color-one: green;
--color-two: blue;
--gradient: linear-gradient(to bottom, var(--color-one), var(--color-two));
}
.container {
background-image: var(--gradient);
}
Браузеры отобразят его от начального цвета green
до конечного blue
. Но что будет, если для элемента с классом box
создать «переменную» --color-one
со значением red
?
:root {
--color-one: green;
--color-two: blue;
--gradient: linear-gradient(to bottom, var(--color-one), var(--color-two));
}
.container {
--color-one: red;
background-image: var(--gradient);
}
Если бы пользовательские свойства были переменными, как в языках программирования, то градиент стал бы от начального цвета red
до конечного blue
. Но браузеры не сохраняют значения пользовательских свойств! Они только передают их.
:root {
--color-one: green;
--color-two: blue;
--gradient: linear-gradient(to bottom, var(--color-one), var(--color-two));
}
.container {
--color-one: red;
background-image: var(--gradient); /* здесь будет значение linear-gradient(to bottom, var(--color-one), var(--color-two)) */
}
По этой причине мы не можем переопределять значение, которое используется в родительском правиле. Так что градиент по-прежнему будет от начального цвета green
до конечного blue
.
▍ !important
не всегда !important
Посмотрите, пожалуйста, на следующий фрагмент кода. Как думаете, каким цветом будет текст?
.container {
--text-color: red !important;
color: var(--text-color);
}
.container {
color: green;
}
Красный? Я тоже так ответил в первый раз, и это неправильный ответ. Браузеры отобразят текст зелёным, и вот почему.
Пользовательские свойства — это полноценные свойства, поэтому если при объявлении значений для них используется !important
, то он имеет приоритет только среди пользовательских свойств с таким именем. Таким образом, в примере при объявлении пользовательского свойства --text-color
!important
создаёт приоритет для него, а не для свойства color
. А поскольку второе правило объявлено ниже по коду, то оно будет иметь приоритет по правилам специфичности.
А если мы будем использовать !important
не при объявлении значения пользовательского свойства, а для свойства color
, то приоритет будет уже у первого правила, поэтому текст уже будет красным.
.container {
--text-color: red;
color: var(--text-color) !important;
}
.container {
color: green;
}
▍ Ключевое слово inherit
Наследование является одним из фундаментальных принципов в CSS, и оно может сбить с толку, когда дело доходит до пользовательских свойств. Рассмотрим пример, где я использую ключевое слово inherit
.
<body>
<div class="parent">
<div class="child">какой-то текст внутри элемента div</div>
</div>
</body>
.parent {
--main-font-size: 50px;
font-size: 30px;
}
.child {
--main-font-size: inherit;
font-size: var(--main-font-size, inherit);
}
Какой размер текста будет у элемента с классом child
? Для правильного ответа нужно помнить, что пользовательские свойства — самодостаточные свойства, и для них также работает механизм наследования.
В нашем примере в строке --main-font-size: inherit
с помощью ключевого слова inherit
произойдёт наследование от пользовательского свойства --main-font-size
, а не от свойства font-size
.
.parent {
--main-font-size: 50px;
font-size: 30px;
}
.child {
--main-font-size: inherit; /* здесь значение 50px */
font-size: var(--main-font-size, inherit);
}
После замены функции var()
браузеры подставят 50px
для свойства font-size
, и в итоге получим font-size: 50px
для элемента с классом child
.
Мы рассмотрели наследование от родительского пользовательского свойства, а что будет, если его не указывать?
.parent {
font-size: 30px;
}
.child {
--main-font-size: inherit;
font-size: var(--main-font-size, inherit);
}
В этом случае браузеры не могут получить значение с помощью ключевого слова inherit
из строки --main-font-size: inherit
, поэтому они ничего не передадут. В этом случае будет использовано значение по умолчанию inherit
, с помощью которого браузеры передадут значение 30px
от свойства font-size
элемента с классом parent
.
.parent {
font-size: 30px;
}
.child {
--main-font-size: inherit; /* здесь нет значения */
font-size: var(--main-font-size, inherit); /* здесь будет font-size: 30px */
}
▍ Вместо заключения
Мне хочется верить, что я помог больше узнать про пользовательские свойства, и вы будете использовать их без опаски. Если у вас был какой-то кейс, не описанный мной, то напишите, пожалуйста, о нём в комментариях. Мне будет интересно прочитать.
Автор: Стас Мельников