Я люблю создавать компоненты везде и всегда, поэтому пользовательские 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. Но что будет, если для элемента с классом container создать «переменную» --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 */
}

▍ Вместо заключения


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


Telegram-канал с розыгрышами призов, новостями IT и постами о ретроиграх ????️

Комментарии (11)


  1. Spaceoddity
    22.08.2023 14:42
    +3

    Честно, я не знаю, почему браузеры могут обрабатывать символы на русском языке.

    UTF-8 и отсутствие запрещенных символов?


    1. acsent1
      22.08.2023 14:42
      +2

      Переменные на национальных языках нынче практически во всех языках программирования допустимы


      1. FoxWMulder
        22.08.2023 14:42

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


    1. melnik909 Автор
      22.08.2023 14:42

      Я тоже так подумал, но меня смутили требования к <ident>, описанные в стандарте CSS Syntax Module Level 3. По правилам мы можем использовать символы [a-zA-Z0-9]. Поэтому я в ступоре.


      1. Spaceoddity
        22.08.2023 14:42
        +1

        Ну стандарт это одно, а браузерный движок - совсем другое))

        Вспомните css-хаки из нулевых.


  1. vanxant
    22.08.2023 14:42

    То, что в идентификаторах допустимы символы юникода я знал (спасибо punto switcher-у у коллеги), про возможность экранирования знал но забыл, но вот var(--\*) прям сделало мой день


  1. ilriv
    22.08.2023 14:42
    +6

    Разработчики CSS, астанавитесь!


    1. FoxWMulder
      22.08.2023 14:42

      вотвот. была понятная и простая технология, но наворотили ТАКОГО. видимо следующий этап это программирование ИИ на CSS.


      1. Spaceoddity
        22.08.2023 14:42

        Не была она простой никогда! Над CSS всё время посмеивались, типа "недоЯП", но там сами абстракции представления заложенные в парадигму CSS - не слишком очевидны для понимания. Говорю вам как человек работавший над сотнями стилей от труЪ-программистов)) Флексбоксы - да, почти все легко приняли (как же меня трясет от условного бутстрапа). Но чуть сложнее (те же гриды) или глубже (понятие нормального потока) - и всё, многие уже считают происходящее какой-то "магией" или "багами"))

        Ну а насчёт "наворотили"... Хм... Ну во-первых, проектирование адаптивных интерфейсов (не привязанных к конкретному устройству отображения) на любой вкус и цвет, разумеется требует довольно серьёзной "базы под капотом". Я успел ещё поверстать "табличной вёрсткой" - нет уж, пускай лучше дальше "наворачивают"))

        А во-вторых, вы же сами (не вы конкретно, а веб-сообщество разработчиков) к этому постоянно подталкиваете. Носились-носились с этим SASS (ещё одно поделие без понимания того, как изначально должен работать CSS) - вот вам всё это сразу по дефолту! Не надо больше шаманских плясок с препроцессорами! Нет, все равно недовольны)) Никто же не принуждает всё это использовать. Нативные веб-технологии тем и хороши, что в них "обратная совместимость" одно из обязательных требований.


  1. atesinde
    22.08.2023 14:42

    Интересно, спасибо! Особенно понравились "задачки" с наследованием)


  1. FoxWMulder
    22.08.2023 14:42

    бред какой-то. я про CSS, не статью. глаза лезут на лоб. логика отдыхает