Пользовательские свойства CSS в каскаде

Опубликовано: 2022-03-10
Краткое резюме ↬ В этой статье Мириам углубляется в спецификацию «Пользовательские свойства CSS для каскадных переменных», чтобы задать вопрос: «Почему они называются пользовательскими свойствами, как они работают в каскаде и что еще мы можем с ними делать». ?» Отбросив метафору «переменная», пользовательские свойства могут предоставить новые способы сбалансировать контекст и изоляцию в шаблонах и компонентах CSS.

В прошлом месяце у меня был разговор в Твиттере о разнице между стилями с ограниченной областью действия (генерируемыми в процессе сборки) и «вложенными» стилями, родными для CSS. Я спросил, почему, как ни странно, разработчики избегают специфики селекторов идентификаторов, используя «стили с ограниченной областью действия», созданные JavaScript? Keith Grant предположил, что разница заключается в балансировании каскада* и наследования, т. е. в отдании предпочтения близости специфичности. Давайте взглянем.

Каскад

Каскад CSS основан на трех факторах:

  1. Важность определяется флагом !important и источником стиля (пользователь > автор > браузер).
  2. Специфика используемых селекторов (встроенный > ID > класс > элемент)
  3. Исходный порядок самого кода (самый последний имеет приоритет)

Близость нигде не упоминается — отношение DOM-дерева между частями селектора. Оба абзаца ниже будут выделены красным цветом, хотя #inner p описывает более тесную связь, чем #outer p для второго абзаца:

См. Pen [Каскад: специфичность против близости] (https://codepen.io/smashingmag/pen/OexweJ/) Мириам Сюзанн.

См. Pen Cascade: Specificity vs Proximity Мириам Сюзанн.
 <section> <p>This text is red</p> <div> <p>This text is also red!</p> </div> </section>
 #inner p { color: green; } #outer p { color: red; }

Оба селектора имеют одинаковую специфичность, они оба описывают один и тот же элемент p , и ни один из них не помечен как !important , поэтому результат основан только на исходном порядке.

Еще после прыжка! Продолжить чтение ниже ↓

БЭМ и стили области действия

Соглашения об именах, такие как BEM («Block__Element — Modifier»), используются для обеспечения того, чтобы каждый абзац «привязывался» только к одному родителю, полностью избегая каскада. Параграфу «элементы» присваиваются уникальные классы, специфичные для их контекста «блока»:

См. Pen [BEM Selectors & Proximity] (https://codepen.io/smashingmag/pen/qzPyeM/) Мириам Сюзанн.

См. «Селекторы и близость Pen BEM» Мириам Сюзанн.
 <section class="outer"> <p class="outer__p">This text is red</p> <div class="inner"> <p class="inner__p">This text is green!</p> </div> </section>
 .inner__p { color: green; } .outer__p { color: red; }

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

См. Pen [Scoped Style Proximity] (https://codepen.io/smashingmag/pen/NZaLWN/) Мириам Сюзанн.

См. книгу Мириам Сюзанн «Близость в стиле пера».
 <section outer-scope> <p outer-scope>This text is red</p> <div outer-scope inner-scope> <p inner-scope>This text is green!</p> </div> </section>
 p[inner-scope] { color: green } p[outer-scope] { color: red; }

Наследование

Близость не является частью каскада, но является частью CSS. Вот где наследование становится важным. Если мы удалим p из наших селекторов, каждый абзац унаследует цвет от своего ближайшего предка:

См. Pen [Наследование: специфичность против близости] (https://codepen.io/smashingmag/pen/mZBGyN/) Мириам Сюзанн.

См. «Наследование пера: специфичность против близости» Мириам Сюзанн.
 #inner { color: green; } #outer { color: red; }

Поскольку #inner и #outer описывают разные элементы, наш div и section соответственно, оба свойства цвета применяются без конфликтов. Цвет вложенного элемента p не указан, поэтому результаты определяются наследованием (цветом прямого родителя), а не каскадом . Близость имеет приоритет, а значение #inner имеет приоритет над значением #outer .

Но есть проблема: чтобы использовать наследование, мы стилизуем все внутри нашего section и div . Мы хотим конкретно настроить цвет абзаца.

(Повторно) введение пользовательских свойств

Пользовательские свойства предоставляют новое решение для браузера; они наследуются, как и любое другое свойство, но их не обязательно использовать там, где они определены . Используя простой CSS, без каких-либо соглашений об именах или инструментов сборки, мы можем создать стиль, который одновременно является целевым и контекстуальным, с близостью, имеющей приоритет над каскадом:

См. Pen [Custom Props: Specificity vs Proximity] (https://codepen.io/smashingmag/pen/gNGdaO/) Мириам Сюзанн.

См. «Пользовательские реквизиты пера: специфичность и близость» Мириам Сюзанн.
 p { color: var(--paragraph); } #inner { --paragraph: green; } #outer { --paragraph: red; }

Пользовательское свойство --paragraph наследуется точно так же, как свойство color , но теперь у нас есть контроль над тем, как и где применяется это значение. Свойство --paragraph действует аналогично параметру, который может быть передан в компонент p либо посредством прямого выбора (правила специфичности), либо контекста (правила близости).

Я думаю, что это раскрывает потенциал для пользовательских свойств, которые мы часто связываем с функциями, примесями или компонентами.

Пользовательские «функции» и параметры

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

 html { --stripes: linear-gradient( to right, powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }

Эта переменная определена в корневом html -элементе (можно также использовать :root , но это добавляет ненужную специфичность), поэтому наша полосатая переменная будет доступна повсюду в документе. Мы можем применить его везде, где поддерживаются градиенты:

См. Pen [Custom Props: Variable] (https://codepen.io/smashingmag/pen/NZwrrm/) Мириам Сюзанн.

См. Pen Custom Props: Variable by Miriam Suzanne.
 body { background-image: var(--stripes); }

Добавление параметров

Функции используются как переменные, но определяют параметры для изменения вывода. Мы можем обновить нашу переменную --stripes , чтобы она была более похожей на функцию, определив внутри нее некоторые переменные, подобные параметрам. Я начну с замены to right на var(--stripes-angle) , чтобы создать параметр, изменяющий угол:

 html { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }

Есть и другие параметры, которые мы могли бы создать, в зависимости от того, для какой цели предназначена функция. Должны ли мы позволять пользователям выбирать свои собственные цвета полос? Если да, то принимает ли наша функция 5 различных цветовых параметров или только 3, которые будут выходить наружу-внутрь, как сейчас? Хотим ли мы также создать параметры для цветовых остановок? Каждый добавляемый нами параметр обеспечивает больше возможностей настройки за счет простоты и последовательности.

Универсального правильного ответа на этот баланс не существует — некоторые функции должны быть более гибкими, а другие — более самостоятельными. Абстракции существуют для обеспечения согласованности и удобочитаемости вашего кода, поэтому сделайте шаг назад и спросите, каковы ваши цели. Что действительно нужно настраивать, и где должна быть обеспечена согласованность? В некоторых случаях может быть полезнее иметь две самостоятельные функции, а не одну полностью настраиваемую функцию.

Чтобы использовать приведенную выше функцию, нам нужно передать значение параметра --stripes-angle и применить вывод к выходному свойству CSS, например background-image :

 /* in addition to the code above… */ html { --stripes-angle: 75deg; background-image: var(--stripes); } 

См. Pen [Custom Props: Function] (https://codepen.io/smashingmag/pen/BgwOjj/) Мириам Сюзанн.

См. «Пользовательские реквизиты ручки: функция» Мириам Сюзанн.

Унаследованное против универсального

Я определил функцию --stripes для элемента html по привычке. Пользовательские свойства наследуются, и я хочу, чтобы моя функция была доступна везде, поэтому есть смысл поместить ее в корневой элемент. Это хорошо работает для наследования таких переменных, как --brand-color: blue , поэтому мы можем ожидать, что это сработает и для нашей «функции». Но если мы попытаемся снова использовать эту функцию во вложенном селекторе, она не сработает:

См. Pen [Custom Props: Function Inheritance Fail] (https://codepen.io/smashingmag/pen/RzjRrM/) Мириам Сюзанна.

См. «Пользовательские реквизиты пера: сбой наследования функций» Мириам Сюзанн.
 div { --stripes-angle: 90deg; background-image: var(--stripes); }

Новый --stripes-angle полностью игнорируется. Оказывается, мы не можем полагаться на наследование для функций, которые необходимо пересчитать. Это связано с тем, что каждое значение свойства вычисляется один раз для каждого элемента (в нашем случае — корневого элемента html ), а затем вычисленное значение наследуется . Определяя нашу функцию в корне документа, мы не делаем всю функцию доступной для потомков — только вычисленный результат нашей функции.

Это имеет смысл, если вы создадите его с точки зрения каскадного параметра --stripes-angle . Как и любое унаследованное свойство CSS, оно доступно потомкам, но не предкам. Значение, которое мы установили для вложенного div , недоступно для функции, которую мы определили для корневого предка html . Чтобы создать общедоступную функцию, которая будет пересчитывать любой элемент, мы должны определить ее для каждого элемента:

См. Pen [Custom Props: Universal Function] (https://codepen.io/smashingmag/pen/agLaNj/) Мириам Сюзанн.

См. «Пользовательские реквизиты для ручек: универсальная функция» Мириам Сюзанн.
 * { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }

Универсальный селектор делает нашу функцию доступной везде, но мы можем определить ее более узко, если захотим. Важно то, что он может пересчитывать только там, где это явно определено. Вот несколько альтернатив:

 /* make the function available to elements with a given selector */ .stripes { --stripes: /* etc… */; } /* make the function available to elements nested inside a given selector */ .stripes * { --stripes: /* etc… */; } /* make the function available to siblings following a given selector */ .stripes ~ * { --stripes: /* etc… */; } 

См. Pen [Custom Props: Scoped Function] (https://codepen.io/smashingmag/pen/JQMvGM/) Мириам Сюзанн.

См. статью Мириам Сюзанн «Пользовательские реквизиты пера: функция с областью действия».

Это можно расширить с помощью любой логики селектора, не зависящей от наследования.

Бесплатные параметры и резервные значения

В нашем примере выше var(--stripes-angle) не имеет значения и резервного варианта. В отличие от переменных Sass или JS, которые должны быть определены или созданы до их вызова, пользовательские свойства CSS можно вызывать, даже не определяя их. Это создает «свободную» переменную, похожую на параметр функции, который может быть унаследован из контекста.

В конечном итоге мы можем определить переменную в html или :root (или любом другом предке), чтобы установить унаследованное значение, но сначала нам нужно рассмотреть запасной вариант, если значение не определено. Есть несколько вариантов, в зависимости от того, какое именно поведение мы хотим

  1. Для «обязательных» параметров нам не нужен запасной вариант. Как есть, функция ничего не будет делать, пока не будет определен --stripes-angle .
  2. Для «необязательных» параметров мы можем предоставить резервное значение в функции var() . После имени переменной мы добавляем запятую, а затем значение по умолчанию:
 var(--stripes-angle, 90deg)

Каждая функция var() может иметь только один запасной вариант, поэтому любые дополнительные запятые будут частью этого значения. Это позволяет указывать сложные значения по умолчанию с внутренними запятыми:

 html { /* Computed: Hevetica, Ariel, sans-serif */ font-family: var(--sans-family, Hevetica, Ariel, sans-serif); /* Computed: 0 -1px 0 white, 0 1px 0 black */ test-shadow: var(--shadow, 0 -1px 0 white, 0 1px 0 black); }

Мы также можем использовать вложенные переменные для создания собственных каскадных правил, присваивая разным значениям разные приоритеты:

 var(--stripes-angle, var(--global-default-angle, 90deg))
  1. Во-первых, попробуйте наш явный параметр ( --stripes-angle );
  2. Возврат к глобальному «пользовательскому умолчанию» ( --user-default-angle ), если он доступен;
  3. Наконец, вернитесь к нашим «заводским настройкам по умолчанию» (90deg ).

См. Pen [Custom Props: Fallback Values] (https://codepen.io/smashingmag/pen/jjGvVm/) Мириам Сюзанн.

См. «Пользовательские реквизиты пера: резервные значения» Мириам Сюзанн.

Установив резервные значения в var() вместо явного определения пользовательского свойства, мы гарантируем, что для параметра не будет ограничений по специфичности или каскадности. Все параметры *-angle «свободны» для наследования из любого контекста.

Резервные варианты браузера по сравнению с резервными вариантами переменных

Когда мы используем переменные, нам нужно помнить о двух запасных путях:

  1. Какое значение следует использовать браузерам без поддержки переменных?
  2. Какое значение должны использовать браузеры, поддерживающие переменные, когда определенная переменная отсутствует или недействительна?
 p { color: blue; color: var(--paragraph); }

В то время как старые браузеры будут игнорировать свойство объявления переменной и вернуться к blue , современные браузеры будут читать и то и другое и использовать последнее. Наша переменная var(--paragraph) может быть не определена, но она действительна и переопределит предыдущее свойство, поэтому браузеры с поддержкой переменных будут возвращаться к унаследованному или начальному значению, как если бы они использовали ключевое слово unset .

Сначала это может показаться запутанным, но на это есть веские причины. Первый — технический: браузерные движки обрабатывают недопустимый или неизвестный синтаксис во время «анализа» (что происходит первым), но переменные не разрешаются до «времени вычисления значения» (что происходит позже).

  1. Во время синтаксического анализа объявления с недопустимым синтаксисом полностью игнорируются, возвращаясь к более ранним объявлениям. Это путь, по которому пойдут старые браузеры. Современные браузеры поддерживают синтаксис переменных, поэтому предыдущее объявление отбрасывается.
  2. Во время вычисления значения переменная компилируется как недопустимая, но уже слишком поздно — предыдущее объявление уже было отброшено. Согласно спецификации, недопустимые значения переменных обрабатываются так же, как и unset :

См. Pen [Custom Props: Invalid/Unsupported vs Undefined](https://codepen.io/smashingmag/pen/VJMGbJ/) Мириам Сюзанн.

См. «Пользовательские реквизиты пера: недопустимые/неподдерживаемые и неопределенные» Мириам Сюзанн.
 html { color: red; /* ignored as *invalid syntax* by all browsers */ /* - old browsers: red */ /* - new browsers: red */ color: not a valid color; color: var(not a valid variable name); /* ignored as *invalid syntax* by browsers without var support */ /* valid syntax, but invalid *values* in modern browsers */ /* - old browsers: red */ /* - new browsers: unset (black) */ --invalid-value: not a valid color value; color: var(--undefined-variable); color: var(--invalid-value); }

Это также хорошо для нас как авторов, потому что позволяет нам экспериментировать с более сложными резервными вариантами для браузеров, поддерживающих переменные, и предоставлять простые резервные варианты для старых браузеров. Более того, это позволяет нам использовать состояние null / undefined для установки необходимых параметров. Это становится особенно важным, если мы хотим превратить функцию в миксин или компонент.

Пользовательское свойство «Миксины»

В Sass функции возвращают необработанные значения, в то время как примеси обычно возвращают фактический вывод CSS с парами свойство-значение. Когда мы определяем универсальное свойство --stripes , не применяя его ни к какому визуальному выводу, результат подобен функции. Мы можем сделать так, чтобы он вел себя как миксин, также определив вывод универсально:

 * { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }

Пока --stripes-angle остается недействительным или неопределенным, миксин не скомпилируется, и background-image не будет применено. Если мы установим допустимый угол для любого элемента, функция вычислит и даст нам фон:

 div { --stripes-angle: 30deg; /* generates the background */ }

К сожалению, это значение параметра будет унаследовано, поэтому текущее определение создает фон для div и всех потомков . Чтобы исправить это, мы должны убедиться, что значение --stripes-angle не наследуется, установив его в initial (или любое недопустимое значение) для каждого элемента. Мы можем сделать это на том же универсальном селекторе:

См. Pen [Custom Props: Mixin] (https://codepen.io/smashingmag/pen/ZdXMJx/) Мириам Сюзанн.

См. Пользовательский реквизит Pen: Mixin от Miriam Suzanne.
 * { --stripes-angle: initial; --stripes: /* etc… */; background-image: var(--stripes); }

Безопасные встроенные стили

В некоторых случаях нам нужно, чтобы параметр задавался динамически извне CSS — на основе данных с внутреннего сервера или внешнего фреймворка. С помощью настраиваемых свойств мы можем безопасно определять переменные в нашем HTML, не беспокоясь об обычных проблемах специфичности:

См. Pen [Custom Props: Mixin + Inline Style] (https://codepen.io/smashingmag/pen/qzPMPv/) Мириам Сюзанн.

См. Pen Custom Props: Mixin + Inline Style от Miriam Suzanne.
 <div>...</div>

Встроенные стили имеют высокую специфичность, и их очень сложно переопределить, но с пользовательскими свойствами у нас есть еще один вариант: игнорировать их. Если мы установим для div значение background-image: none (например), эта встроенная переменная не окажет никакого влияния. Чтобы пойти еще дальше, мы можем создать промежуточную переменную:

 * { --stripes-angle: var(--stripes-angle-dynamic, initial); }

Теперь у нас есть возможность определить --stripes-angle-dynamic в HTML или проигнорировать его и установить --stripes-angle непосредственно в нашей таблице стилей.

См. Pen [Custom Props: Mixin + Inline / Override] (https://codepen.io/smashingmag/pen/ZdXMao/) Мириам Сюзанн.

См. Пользовательские реквизиты Pen: Mixin + Inline / Override Мириам Сюзанн.

Предустановленные значения

Для более сложных значений или общих шаблонов, которые мы хотим использовать повторно, мы также можем предоставить несколько предустановленных переменных на выбор:

 * { --tilt-down: 6deg; --tilt-up: -6deg; }

И используйте эти пресеты, а не устанавливайте значение напрямую:

 <div>...</div> 

См. Pen [Custom Props: Mixin + Presets] (https://codepen.io/smashingmag/pen/LKemZm/) Мириам Сюзанн.

См. Pen Custom Props: Mixin + Presets от Miriam Suzanne.

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

См. Pen [Гистограмма в сетке CSS + переменные] (https://codepen.io/smashingmag/pen/wLrEyg/) Мириам Сюзанн.

См. диаграмму Pen Bar в сетке CSS + переменные Мириам Сюзанн.

Контекстные компоненты

Мы также можем преобразовать наш «примесь» в «компонент», применив его к явному селектору и сделав параметры необязательными. Вместо того, чтобы полагаться на наличие или отсутствие --stripes-angle для переключения нашего вывода, мы можем полагаться на наличие или отсутствие селектора компонентов. Это позволяет нам безопасно устанавливать резервные значения:

См. Pen [Custom Props: Component](https://codepen.io/smashingmag/pen/QXqVmM/) Мириам Сюзанн.

См. Pen Custom Props: Component by Miriam Suzanne.
 [data-stripes] { --stripes: linear-gradient( var(--stripes-angle, to right), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }

Поместив запасной вариант внутрь функции var() , мы можем оставить --stripes-angle неопределенным и «свободным» для наследования значения извне компонента. Это отличный способ представить определенные аспекты стиля компонента для контекстного ввода. Даже стили с ограниченной областью действия, сгенерированные фреймворком JS (или ограниченные областью действия внутри теневой модели DOM, например значки SVG), могут использовать этот подход для предоставления определенных параметров внешнему влиянию.

Изолированные компоненты

Если мы не хотим предоставлять параметр для наследования, мы можем определить переменную со значением по умолчанию:

 [data-stripes] { --stripes-angle: to right; --stripes: linear-gradient( var(--stripes-angle, to right), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }

Эти компоненты также будут работать с классом или любым другим допустимым селектором, но я выбрал атрибут data- , чтобы создать пространство имен для любых модификаторов, которые нам нужны:

 [data-stripes='vertical'] { --stripes-angle: to bottom; } [data-stripes='horizontal'] { --stripes-angle: to right; } [data-stripes='corners'] { --stripes-angle: to bottom right; } 

См. Pen [Пользовательские реквизиты: изолированные компоненты] (https://codepen.io/smashingmag/pen/agLaGX/) Мириам Сюзанн.

См. «Пользовательские реквизиты пера: изолированные компоненты» Мириам Сюзанн.

Селекторы и параметры

Мне часто хотелось бы использовать атрибуты данных для установки переменной — функция, поддерживаемая спецификацией CSS3 attr() , но еще не реализованная ни в одном браузере (см. вкладку ресурсов для связанных проблем в каждом браузере). Это позволило бы нам более тесно связать селектор с конкретным параметром:

 <div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); } <div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); } <div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); }

Тем временем мы можем добиться чего-то подобного, используя атрибут style :

См. Pen [Custom Props: Style Selectors] (https://codepen.io/smashingmag/pen/PrJdBG/) Мириам Сюзанн.

См. Pen Custom Props: Style Selectors by Miriam Suzanne.
 <div>...</div> /* The `*=` atttribute selector will match a string anywhere in the attribute */ [style*='--stripes-angle'] { /* Only define the function where we want to call it */ --stripes: linear-gradient(…); }

Этот подход наиболее полезен, когда мы хотим включить другие свойства в дополнение к устанавливаемому параметру. Например, установка области сетки может также добавить отступы и фон:

 [style*='--grid-area'] { background-color: white; grid-area: var(--grid-area, auto / 1 / auto / -1); padding: 1em; }

Заключение

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

Это требует от нас переосмысления многих инструментов, на которые мы полагались в прошлом — от соглашений об именах, таких как SMACSS и BEM, до стилей с ограниченной областью действия и CSS-in-JS. Многие из этих инструментов помогают обойти специфику или управлять динамическими стилями на другом языке — варианты использования, которые теперь мы можем решать напрямую с помощью пользовательских свойств. Динамические стили, которые мы часто рассчитывали в JS, теперь можно обрабатывать, передавая необработанные данные в CSS.

Поначалу эти изменения могут восприниматься как «дополнительная сложность» — поскольку мы не привыкли видеть логику внутри CSS. И, как и в случае со всем кодом, чрезмерная разработка может быть реальной опасностью. Но я бы сказал, что во многих случаях мы можем использовать эту силу не для усложнения , а для того, чтобы переместить сложность из сторонних инструментов и соглашений обратно в основной язык веб-дизайна и (что более важно) обратно в язык. браузер. Если наши стили требуют вычислений, эти вычисления должны находиться внутри нашего CSS.

Все эти идеи можно развить гораздо дальше. Пользовательские свойства только начинают получать более широкое распространение, а мы только начали исследовать возможности. Я взволнован, чтобы видеть, куда это идет, и что еще люди придумывают. Повеселись!

Дальнейшее чтение

  • «Пришло время начать использовать пользовательские свойства CSS», Серг Хосподарец
  • «Стратегическое руководство по пользовательским свойствам CSS», Майкл Ритмюллер.