Illustrated CSS: CSS Custom Properties

w3cplus
46 min readMar 21, 2024

As we all know, maintaining CSS has always been a challenging task, especially when building large web sites or applications. If multiple people collaborate, the difficulty will be even greater. In addition, because CSS language is a declarative language, unlike other programming languages that have functions such as variables, conditions, and logic, it has always been at a lower level of programming languages.

It is precisely for this reason that various CSS processor languages have emerged in the community, such as Sass, LESS, and Stylus. These processor languages introduce some features similar to other programming languages, such as variables, operators, and logical operations.

Although CSS processors have brought some convenience to writing and maintaining CSS, additional compile steps are still needed. However, variables in processors bring us huge advantages. It is for this reason that the community began to introduce variables from CSS processors into native CSS. After years of promotion and evolution, today’s CSS custom properties have emerged.

Next, in this chapter, we will explore CSS custom properties and their applications and advantages.

Introduction to CSS Custom Properties

CSS custom properties have entered the TR phase of the W3C specification and are included in a separate module, CSS Custom Properties for Cascading Variables Module Level 1 . This module introduces a series of author-defined properties, collectively referred to as custom properties , allowing authors to freely choose names and assign arbitrary values to name properties. These properties can be provided to the var() function, and the custom properties referenced by the var() function are often referred to as variables .

In this way, these free properties declared by CSSer have two names: custom properties and variables :

  • Custom properties : Use --* ( * represents any declared name) to declare a special format as the name, which is called a custom property , and any value can be assigned to the custom property. For example --color: #fff .
  • Variables : The custom properties referenced by the var() function in CSS are called variables . var() returns the value corresponding to the custom property and can be applied to the corresponding CSS property. The corresponding is the property value in the CSS rules.

Describe their relationship with a picture:

The role of CSS custom properties

If you have used any programming language, the term variable (concept) is not unfamiliar. In some imperative programming languages (such as JavaScript, which we are familiar with in the front end), variables can help us better track certain states. Variables are symbols associated with a specific value, and the value of a variable can change over time. The advantage of variables is that we can store the value in one place and then call or modify it where needed. This way, we don’t have to add different variables for different values in different parts of the program.

All variable updates use the same storage address .

In a declarative language like CSS, it lacks dynamism. It is also impossible to achieve that values that change over time do not exist, so there is no concept of variables . However, in fact, we are very much looking forward to CSS being able to make continuous changes with the surrounding environment and the needs of developers, just like other programming languages.

Before the appearance of CSS custom properties, CSS introduced the concept of hierarchical variables (which will be mentioned later), which enabled it to cope with maintainability challenges with ease. This allows a variable to be symbolically referenced throughout the CSS Tree. However, this symbolic variable often cannot solve the problem of maintaining and extending CSS well, and it is enough to cause headaches for some colleagues who do not understand CSS. That’s another story for now. Let’s continue back to our actual scenario.

I think many colleagues should have experience building large-scale web websites or web applications. The amount of CSS used is very large, and there is a lot of reuse in many occasions. Take the color scheme of a website as an example. Some colors will appear many times in the CSS file and be reused. When you modify the color scheme, whether it is adjusting a certain color or completely modifying the entire color scheme, it will be a complex problem. Simply relying on global search and replacement is far from enough, and such operations are inevitably prone to errors.

If a CSS framework is used, this situation will become particularly bad. In this case, if you want to modify the color, you need to modify the framework itself. Although these frameworks may have introduced CSS processors like Sass to help us reduce the chance of errors and improve maintainability, adding additional steps (requiring compile processing) may increase the complexity of the system.

The emergence of CSS custom properties (CSS variables) has brought us some convenience of CSS processors and does not require additional compile. The benefits of using CSS custom properties in CSS are not particularly different from those of using variables in programming languages. There is a description in the W3C specification as follows:

The use of CSS custom properties makes large files easier to read, because seemingly random values have a hint name, and editing these files is simpler and less error-prone. Because you only need to modify the custom property once, this modification will be applied wherever the custom property is used .

Simply put, CSS custom properties not only provide more flexible settings, references, and modifications, but also have strong semantics (which requires you to have a strong awareness of semantics, such as names like primary are always more meaningful than names like red ). These semantic information make your CSS files easy to read and understand.

For this reason, readability and maintainability are the biggest advantages of CSS custom properties .

CSS custom property syntax and basic applications

When introducing CSS custom properties, we can start with something familiar, which makes it easier for colleagues who come from the backend to understand. Let’s take variables in JavaScript as an example.

In JavaScript, there are multiple ways to declare a variable, such as:

var customProperty
// 或
let customProperty = true
// 还可以
const customProperty = IS_ACTIVE

If you are as unfamiliar with JavaScript as I am, or if you have had some contact with CSS processors, we can take CSS processors as an example. Several CSS processors we are familiar with, such as LESS, Sass, and Stylus, have their own way of declaring variables. Usually, a symbolic entity character is used as a variable prefix, such as the $ character in Sass, the @ character in LESS, and the expression directly used by Stylus without a special prefix, such as primary-color = red .

CSS custom properties use a similar approach when declared, introducing the -- symbol as a prefix to declare a custom property:

:root {
--primary: #f36;
}

The --primary in the example is what we call a CSS custom property . CSS custom properties are used the same way as regular CSS properties. It is better to treat them as dynamic properties than variables. This means they can only be used in the declaration block. That is, custom properties and selectors are strongly bound . Can be any valid selector.

If the declared CSS custom property is not called by any property, it will have no effect. Only a string will remain in your style file.

Calling declared CSS custom properties is slightly different from calling variables in other CSS handlers. Calling CSS custom properties requires referencing through the var() function. Pass the CSS custom property as the first parameter of the var() function and assign the entire function to the CSS property (which can be a CSS property or a CSS custom property), for example:

body {
color: var(--primary);
}

.button {
--primaryButton: var(--primary);
}

The var() function in the example can replace any part of the value in any attribute of the element. However, the var() function cannot be used as an attribute name , selector or any other value other than the attribute value .

The var () function can accept two values at the same time:

var(<custom-property-name>, <declaration-value>)

Among them, <custom-property-name> is a CSS custom property; <declaration-value> is a fallback value, which is used to ensure that the var() function has a value when the custom property value is invalid, so that CSS property rules can take effect. For example:

:root {
--primary: #f36;
}

.button {
background-color: var(--primary, #fff);
color: var(--color, #333);
}

Usage of CSS custom properties

Now that I have a basic understanding of CSS custom properties, I will use a few small example codes to demonstrate some of the features of CSS custom properties to enhance my understanding.

CSS custom properties and CSS properties work exactly the same way

CSS custom properties can be declared as ordinary properties on any element, selector, or even pseudo-element. Its usage is the same as that of CSS properties, and the principle is also the same.

:root {
--font-size: 1em;
}
p {
font-size: var(--font-size);
}
section::after {
font-size:1.5em;
}

CSS custom properties, like CSS properties, have inheritance and cascading characteristics

There are three concepts in CSS that must be mastered when learning CSS, namely cascading , inheritance and weight . CSS custom properties also have inheritance and cascading characteristics. For example:

<!-- HTML -->
<div class="parent">
<div class="child1"></div>
<div class="child2"></div>
</div>
.parent {
--primary: #f36;
}
.child1 {
background-color: var(--primary);
}
.child2 {
color: var(--primary);
}

In the above example, .child1 and .child2 both inherit the --primary custom attribute from their parent element .parent . But in many cases, we don't need this behavior, we can explicitly declare the custom attribute in :root {} :

:root {
--primary: #f36;
}

.child1 {
background-color: var(--primary);
}
.child2 {
color: var(--primary)
}

This may not be easy to understand, so let’s take a more realistic example. For example, every web application has its own color scheme. Take the color scheme of Bootstrap, a CSS framework, for example. Its main color scheme is --primary: #007bff , which is used in multiple places, such as:

Using the inherited features of CSS can make things easier.

:root{
--primary: #007bff;
}

/* Button组件 */
.btn-primary {
background-color: var(--primary);
border-color: var(--primary);
}
/* Badge组件 */
.badge-primary {
background-color: var(--primary);
border-color: var(--primary);
}
/* Dropdowns组件 */
.dropdown-primary {
background-color: var(--primary);
border-color: var(--primary);
}
/* Pagination组件 */
.page-link {
color: var(--primary);
}
/* Progress组件 */
.progress-bar {
background-color: var(--primary);
}

If one day your boss says you don’t want to look at this color anymore and want to change the color system, just adjust the value of --primary in : root .

In addition, the same module components have only slight differences, such as the effect shown in the following figure.

For such an effect, with the cascading feature of CSS, things can also be made easier.

:root {
--color: #333;
}

.card {
color: var(--card);
&:nth-child(2) {
--color: #2196F3;
}
&:nth-child(3) {
--color: #f321ab;
}
}

Due to the complexity of CSS cascading and inheritance, in order to better demonstrate the characteristics of CSS custom properties using CSS cascading and inheritance (with less code, easier maintenance, and easier expansion), I will show you a more hierarchical example.

<!-- HTML -->
<p>我是什么颜色?</p>
<div>我又是什么颜色?</div>
<div id="alert">
我是什么颜色?
<p>我又是什么颜色?</p>
</div>
:root {
--color: #333;
}
div {
--color: #2196F3;
}
#alert {
--color: #f321ab;
}

The results are illustrated by the following figure:

CSS custom properties can be used in inline style properties

CSS custom properties, like CSS properties, can be used in the style property of an element.

<!-- HTML -->
<button style="--color: blue">Click Me</button>
button {
border: 1px solid var(--color);
}

button:hover {
background-color: var(--color);
}

Using CSS custom properties in internal connection styles is very meaningful, especially when manipulating CSS custom properties through JavaScript.

CSS custom properties are case-sensitive

CSS custom properties are slightly different from CSS properties. CSS properties are not case-sensitive, but CSS custom properties are case-sensitive.

:root {
--COLOR: #fff;
--color: #f36;
}

.box {
color: var(--COLOR;
background-color: var(--color);
}

CSS custom property naming

CSS custom attribute naming rules are relatively loose, can be any valid characters, such as Chinese , capital letters , camel naming , medium distance line , emoji and HTML entities, etc. :

CSS custom properties support fallback parameters

When introducing CSS custom properties, if CSS custom properties are passed as parameters to the var() function, it also supports a second parameter, the fallback parameter . CSS properties do not support this feature.

  • If CSS custom properties are not supported by the browser, a degraded parameter can be provided for the browser to recognize
  • If the browser supports CSS custom properties but does not explicitly declare the value of the CSS custom property, the degraded parameter will be selected
  • If the browser supports CSS custom properties and explicitly declares the value of the CSS custom property, the value of the CSS custom property will be selected and the degraded parameter will not be selected

For example, the following example:

:root {
--color: #f36;
}

.box {
width: var(--w, 100px);
color: var(--color, #fff);
border-width: var(--color, 2px);
}

What will happen to invalid CSS custom properties?

What happens when an invalid CSS custom property is applied to a CSS property? Before telling you what happens, let’s take a look at what happens when an invalid value is used in a CSS property.

I think we have all experienced mistakes, for example, when doing CR (Code Review), we will find such phenomena, such as:

.card {
padding: -10px;
}

And padding does not support negative values, that is to say, -10px is an invalid value for the padding property. At this time, the browser will use the initial value of padding ( initial ) when rendering, that is 0 :

When using CSS custom properties, if the CSS custom property is an invalid value for the CSS property when it is called, initial will also be used as a degradation process, for example:

:root {
--color: 20px;
}

.p {
color: var(--color);
}

In the example above, the declared --color: 20px is a valid value, but when it is used for the color attribute, --color is an invalid value because 20px is an invalid value for the color attribute. In this case, the color attribute will take its initial value initial (depending on the user agent). If the parent element of the element does not explicitly set the value of color , it will inherit the <html> element's color value, which will be a #000 color value in Chrome browsers.

There is another scenario where, although calling a declared CSS custom property is an invalid value, a degraded value is provided, and the degraded value is a valid value, then the initial value will not be used, but The degraded value will be used, for example:

:root {
--color: 20px;
}

p {
color: var(--color, blue);
}

Chained CSS custom properties

When using the var() function to call a declared CSS custom property, when providing degraded parameters to var() , we can provide degraded parameters in a chained manner, for example:

p {
--color1: red;
--color2: blueviolet;
--color3: orange;
color: var(--color1, var(--color2, var(--color3, blue)));
}

The CSS custom property of the circular dependency is invalid

CSS is a declarative language, and the style rules of elements have no concept of order ( the same property appears within the same selector block, and the latter will override the former ). It can only have one value, and it cannot be both the previous value and its value plus 1 , so this forms a loop.

Let’s start with JavaScript, for example:

var a = 1;
var a = a;
console.log(a); // » 1

Let’s take a look at the circular use of CSS custom properties again.

:root {
--size: 10px;
--size: var(--size);
}

body {
font-size: var(--size, 2rem);
}

In CSS, the same CSS property is in the same selector block, and the latter will override the former. For example, in the above example, --size: var(--size) will override --size . But if the CSS custom property depends on itself, that is, it uses the var() that references itself, the value is invalid. In the above example, the --size custom property is invalid. It is invalid when calling --size in the body , and the degraded value of var() is 2rem .

In addition to custom properties referencing themselves, there is another scenario where two or more custom properties refer to each other.

:root {
--one: calc(var(--two) + 10px);
--two: calc(var(--one) - 10px);
}

This kind of cross-referencing CSS custom property is also invalid. The only way to break it is: do not create CSS custom properties with circular dependencies in your code .

CSS custom properties with basic operators

Colleagues familiar with CSS processors should know that operator-related operations can be used in CSS processors. This feature can actually be used in CSS with the help of the calc() function to perform some basic operator-related operations. The same feature is found in CSS custom properties:

:root {
--indent-size: 10px;
--indent-xl: calc(var(--indent-size) * 2);
--indent-l: calc(var(--indent-size) + 2px);
--indent-s: calc(var(--indent-size) - 2px);
--indent-xs: calc(var(--indent-size) / 2);
}
p {
text-indent: var(--indent-xl);
}

When performing basic operator operations in CSS custom properties, one thing to note is that values without units cannot be calculated :

:root {
--gap: 10;
}

.card {
padding: var(--gap)px 0; /* 无效,不能正常运行 */
padding: calc(var(--gap) * 1px) 0; /* 有效,可以正常运行 */
}

The value of a CSS custom property is a data, not a property value

In the above example, we saw that we can assign a pure numerical value to a CSS custom property instead of a CSS property value. This has a good advantage, which is that we can easily turn it into the value we want. For example:

.block {
--size: 40;
width: var(--size)vw;
}

As mentioned earlier, this operation is an invalid value ( var(--size) vw ). As mentioned earlier, when a CSS custom property is invalid, the value of the CSS property will take the initial value of the property, such as the width in the above example will take the value of auto . Since the browser considers everything as tokens, it sets the --size value to number and vw to identifier , so it will parse the var(--size) vw in the above example to 40vw (with a space in the middle), instead of the expected 40vw (which ultimately uses the initial value of width as auto ).

We can use the calc() function to solve this problem:

.block {
--size: 40;
width: calc(var(--size) * 1vw); // => width: 40vw
height: calc(var(--size) * 1vh); // => height: 40vh
}

Many colleagues like Aim for the Highest when coding, and may want to save at least one calc () function calculation by using --size: 40vw , for example:

.block {
--size: 40vw;
width: var(--size);
height: calc(var(--size) / 1vw * 1vh);
}

We hope to remove the vw unit by vw/vw , turn --size into a pure number, and then multiply by 1vh to become height: 40vh . But the result is not what we expected, because calc() does not support dividing by values with length units (such as px , vw , vh , etc.), calc() only supports dividing by pure numbers , so the height attribute references an invalid value and uses its initial value, which is auto .

calc() does not support dividing by values with units, so it can only be divided by numbers.

The basic operations between calc() and CSS custom properties mainly depend on the rules of the calc() function:

  • If the custom property is a pure number, calc() can convert it to any unit value, just multiply the value of the CSS custom property by 1 and that unit, such as calc(var(--size) * 1px) , will generate 40px
  • If a custom property has a unit, calc() has no way of converting it to a pure number (perhaps using JavaScript)
  • If the custom property has a unit, calc() can be multiplied or divided by a pure number without any units
  • If the custom property has a unit, calc() can add or subtract any value with a unit

Use of CSS custom properties in @ rules

CSS has some @ rules, such as @charset , @import , @namespace , @document , @font-face , @keyframes , @media , @page , @supports , @viewport and @color-profile . Among them, @media , @supports and @keyframes are common @ rules.

@Media and @supports are conditionals that can be used in CSS; @keyframes are used for CSS animation . Next, we will mainly look at the use of CSS custom properties in @media , @supports and @keyframes .

Use of CSS custom properties in @media

CSS customization for media queries @media , you can dynamically change the value of CSS custom properties, such as:

body {
--color: #f36;
background-color: var(--color);
}

@media screen and (max-width: 375px) {
body {
--color: #9f3;
}
}

The effect is as follows:

The example demonstrated above is one of the simplest, in fact, the combination of CSS custom properties and @media makes responsive web design much simpler.

CSS custom properties in @keyframes use

CSS custom properties and CSS animations work well together. However, be sure to explicitly declare CSS custom properties in animation elements and use var() in @keyframes to reference the declared CSS custom properties. The benefit of doing this is that you can modify the CSS custom properties in the target selector without having to look up each property in @keyframes .

.animate {
--from-color: red;
--to-color: lime;
width: 100px;
height: 100px;
animation: blink 1s infinite;
}

@keyframes blink {
from {
background-color: var(--from-color);
}
to {
background-color: var(--to-color);
}
}

The effect is as follows:

CSS custom properties in @supports use

As of this writing, browser support for CSS custom properties is pretty good, almost 90% :

We can use @supports to make conditional judgments to some extent:

body {
background-color: #f4f4f4;
}

@supports(--css:variables) {
body {
--bg-color: linear-gradient(to bottom, #f36, #f4f4f4);
background: var(--bg-color);
}
}

Application of CSS custom properties in web components

Web components are now widely used in the development of web websites or web applications, and many teams or individuals have made relevant preparations. In fact, when developing or designing web components, combining CSS custom properties will make the entire design more flexible.

We take the Button UI component of the most influential CSS Framework ( Bootstrap ) in the community as an example to explain the application of CSS custom properties in Web components.

Let’s first take a look at its HTML structure.

<!-- HTML -->
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-light">Light</button>
<button type="button" class="btn btn-dark">Dark</button>
<button type="button" class="btn btn-link">Link</button>

As you can see, each button has two class names, one is the basic class name .btn ; the other is the extended class name, such as .btn-primary . Its style:

.btn {
display: inline-block;
font-weight: 400;
color: #212529;
text-align: center;
vertical-align: middle;
user-select: none;
background-color: transparent;
border: 1px solid transparent;
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: .25rem;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}

.btn:hover {
color: #212529;
text-decoration: none;
}
.btn:focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
}
.btn-primary {
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.btn-primary:hover {
color: #fff;
background-color: #0069d9;
border-color: #0062cc;
}
.btn-primary:focus {
box-shadow: 0 0 0 0.2rem rgba(38,143,255,.5);
}
.btn-primary:not(:disabled):not(.disabled):active {
color: #fff;
background-color: #0062cc;
border-color: #005cbf;
}
.btn-primary:not(:disabled):not(.disabled):active:focus {
box-shadow: 0 0 0 0.2rem rgba(38,143,255,.5);
}

For other UI style buttons, adjust by extending the class name, such as .btn-danger :

.btn-danger {
color: #fff;
background-color: #dc3545;
border-color: #dc3545;
}

.btn-danger:hover {
color: #fff;
background-color: #c82333;
border-color: #bd2130;
}
.btn-danger:focus {
box-shadow: 0 0 0 0.2rem rgba(225,83,97,.5);
}
.btn-danger:not(:disabled):not(.disabled):active {
color: #fff;
background-color: #bd2130;
border-color: #b21f2d;
}
.btn-danger:not(:disabled):not(.disabled):active:focus{
box-shadow: 0 0 0 0.2rem rgba(225,83,97,.5);
}

Split the above code into two parts, HTML structure and basic button style.

Another part is the extended style, taking primary and danger as examples:

From the example code split above, different styles of buttons have the same CSS properties, just with different values, such as color , background-color , border-color , box-shadow . In this way, we can extract these same CSS properties as CSS custom properties. For example:

.btn {
--color: #212529;
--background-color: transparent;
--border-color: transparent;
--box-shadow-color: rgba(0, 123, 255, .25);
display: inline-block;
font-weight: 400;
text-align: center;
vertical-align: middle;
user-select: none;
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: .25rem;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
color: var(--color);
background-color: var(--background-color);
border: 1px solid var(--border-color);
}
.btn:hover {
--color: #212529;
text-decoration: none;
}
.btn:focus {
outline: 0;
box-shadow: 0 0 0 .2rem var(--box-shadow-color);
}

This way, when we change the button style, it will appear much simpler.

.btn-primary {
--color: #fff;
--background-color: #007bff;
--border-color: #007bff;
}

.btn-primary:hover {
--background-color: #0069d9;
--border-color: #0062cc;
}
.btn-primary:focus {
--box-shadow-color: rgba(38, 143, 255, .5);
}
.btn-primary:not(:disabled):not(.disabled):active {
--background-color: #0062cc;
--border-color: #00fcbf;
}
.btn-primary:not(:disabled):not(.disabled):active:focus {
--box-shadow-color: rgba(38, 143, 255, .5);
}

Application of CSS custom properties in SVG

CSS custom properties and SVG can work well together.

You can use CSS custom properties to modify styling and presentation-related properties in internal connection SVG .

For example, when using the same icon in different places but with different colors, you can set CSS custom properties on the container elements of the icon and assign different property values. For example:

<svg xmlns="http://www.w3.org/2000/svg" class="icon-container">
<symbol id="icon-close" viewbox="0 0 1024 1024">
<path fill="var(--icon-color)" d="M512 1023.089778C230.172444 1023.089778 0.896 793.813333 0.896 512S230.172444 0.910222 512 0.910222 1023.089778 230.172444 1023.089778 512c0 281.813333-229.319111 511.089778-511.104 511.089778z m0-971.093334C258.318222 51.996444 51.968 258.360889 51.968 512c0 253.653333 206.364444 460.003556 460.003556 460.003556 253.639111 0 459.975111-206.364444 459.975111-460.003556 0-253.653333-206.307556-460.003556-459.975111-460.003556zM692.366222 717.937778a25.457778 25.457778 0 0 1-18.076444-7.495111L511.815111 547.982222 349.653333 710.4a25.528889 25.528889 0 1 1-36.053333-36.053333L476.017778 511.857778 313.543111 349.681778a25.557333 25.557333 0 0 1 36.124445-36.124445L512.142222 476.003556l162.161778-162.446223a25.528889 25.528889 0 1 1 36.053333 36.053334L547.982222 512.099556l162.474667 162.204444a25.557333 25.557333 0 0 1-18.033778 43.648h-0.028444z" />
</symbol>
</svg>

Use the < symbol > tag to create internal connection SVG Sprites (although this example only uses a close icon). Then you can use the xlink: href of < use > to reference the id value in < symbol > :

<svg role="img">
<use xlink:href="#icon-close" />
</svg>

In the <symbol> icon created in SVG, there are style properties about the icon that can be set using CSS custom properties, such as fill = "var(--icon-color) " . This way, when we use it, we can adjust the value of --icon-color similar to the Button introduced earlier, and we can get icons of different colors, such as:

The entire effect is as follows:

Use CSS custom properties to imitate non-existent CSS rules

CSS custom properties also allow us to better imitate non-existent CSS rules, such as box-shadow-color , text-shadow-color , background-sprites , etc. Take CSS box-shadow as an example. CSS box-shadow has a shadow color, but there is no independent property in CSS to set the shadow color. At this time, CSS custom properties can come in handy:

.box {
--box-shadow-color: rgba(0,0,0,.25);
width: 100px;
height: 100px;
bakcground-color: #fff;
box-shadow: .25em .25em .5em var(--box-shadow-color);

&:hover {
--box-shadow-color: rgba(120, 220, 250, .25);
}
}

When hovering the mouse, changing --box-shadow-color changes the shadow color, which is equivalent to imitating the properties of a shadow color:

Let’s look at a more complex case, simulating background-sprites :

.sprites{
--sprites-color: rgba(243, 17, 17, 0.4);
--background-sprites: linear-gradient(-45deg, var(--sprites-color) 25%, transparent 25%, transparent 50%, var(--sprites-color) 50%, var(--sprites-color) 75%, transparent 75%, transparent);
background-image: var(--background-sprites);
background-size: 35px 35px;
width: 50vw;
height: 35px;
border: 1px solid #ccc;
border-radius: 5px;
margin: 10px;
&:hover {
--sprites-color: #57aed1;
}
}

Manipulate CSS custom properties using JavaScript

CSS custom properties, like CSS properties, can be manipulated through some APIs in CSSOM.

  • Use .style.getPropertyValue(--foo) to get CSS custom properties from internal connection styles
  • Use getComputedStyled(element).getPropertyValue(--) to get CSS custom properties from anywhere
  • Use .style.setProperty('--foo', 'red') to set CSS custom properties to internal connection style

The first two are used to obtain CSS custom properties, while the latter is used to set CSS custom properties.

In fact, by combining CSS custom properties with several simple CSSOM APIs, it is easy to implement some motion graphics. Taking @Val Head’s example on CodePen as an example:

HTML structure is very simple.

<!-- HTML -->
<div class="ball">
<div class="halo"></div>
<div class="halo"></div>
<div class="halo"></div>
</div>

Several CSS custom properties are explicitly declared in : root :

:root {
--mouse-x; // » 鼠标 x 轴坐标值
--mouse-y; // » 鼠标 y 轴坐标值
--scale; // » 缩放值
--radius: 40px; // » 圆角半径
--factor: 1; // » 缩放因子
}

Basic style:

.ball {
background: #D92659;
border-radius: 50%;
position:absolute;
width: var(--radius);
height: var(--radius);
transform: translate(calc(var(--mouse-x) * 1px - var(--radius)/2),calc(var(--mouse-y) * 1px - var(--radius)/2));
}
.halo {
background: rgb(114, 61, 83);
border-radius: 50%;
position:absolute;
opacity: .15;
width: var(--radius);
height: var(--radius);
filter: blur(var(--factor));
transform: scale(calc(var(--scale) * var(--factor)));
}
.halo:nth-of-type(1) {
--factor: .3;
}
.halo:nth-of-type(2) {
--factor: 0.5;
}
.halo:nth-of-type(3) {
--factor: .9;
}

Add a few lines of simple code in JavaScript to control the declared CSS custom properties.

var [xpos,targetX,ypos,targetY, velX, velY] = [0,0,0,0,0,0];
const docStyle = document.documentElement.style;
const drag = 0.8;
const strength = 0.12;
function springItOn() {
var diffX = targetX - xpos;
diffX *= strength;
velX *=drag;
velX += diffX;
xpos += velX;
var diffY = targetY - ypos;
diffY *= strength;
velY *=drag;
velY += diffY;
ypos += velY;
docStyle.setProperty('--mouse-x', xpos);
docStyle.setProperty('--mouse-y', ypos);
docStyle.setProperty('--scale', (velY + velX)*strength);
requestAnimationFrame(springItOn);
}
springItOn();
// 跟随鼠标的坐标值来更新目标元素的位置
document.addEventListener('mousemove', (e) => {
targetX = e.clientX;
targetY = e.clientY;
});

At this time, when you move the mouse on the screen, the sphere will also move with zooming, halo and other effects.

Here’s another example written by @Hubert Souchaud :

Use CSS custom properties to change the way we construct CSS

CSS custom properties have the potential to change the way we write and think about CSS .

The emergence of CSS custom properties allows us to write cleaner, more compact, and more flexible CSS, and it may even change the way we write and think about CSS. Next, we will discuss with you through a few small points how CSS custom properties change the way we construct CSS .

Separating logic from design

The main advantage is that we now have the ability to completely separate logic from design. This actually means separating CSS custom property declarations from property declarations.

/* CSS自定义属性声明 */
:root {
--my-var: red;
}

/* CSS属性声明 */
body {
background-color: var(--my-var);
}

We have always advocated in CSS processors that variable declarations and CSS property declarations should be separated . This should not change in the practice of CSS custom properties. We should also separate CSS custom properties and CSS properties when declaring them.

Folding logic

The idea of declaring custom properties at the top of a document or function has been around for a long time. It is also the recommended practice in most languages, and now we can do it in CSS too. Writing CSS in this way makes it easy to visually distinguish between the top and the following code. I can easily find them when I want to use them. This is called “folding logic” .

All CSS custom properties are included above this fold. This makes it easier to know what changes have occurred in custom properties and makes CSS code more readable. Let’s take a look at a small example:

.row  {
--row-display: block;
}

@media screen and (min-width: 30em) {
.row {
--row-display: flex;
}
}

The code under the folding area can look like this:

.row {
display: var(--row-display);
flex-direction: row;
flex-wrap: nowrap;
}

Change the value instead of CSS custom properties

In most cases, if a media query or CSS selector replaces one CSS custom property with another, it is not a good way. I think instead of exchanging CSS custom properties, it is better to define a CSS custom property, set its initial value, and use selectors or media queries to change it.

If it changes, it is a CSS custom property

I believe that in most cases, responsive design logic should be included in CSS custom properties. There is also a strong argument that when any value is changed, whether it is in media queries or element scope, it belongs to a custom property. If it changes, it is a CSS custom property by definition, and this logic should be separated from the design.

Fewer media inquiries

It makes sense to have all the logic related to CSS custom properties at the top of the document (i.e. :root ). It's easier to maintain because you can change it in one place, and it's easier to read. Because you can see what's being changed without reading the entire stylesheet.

We cannot do this for media queries because it separates the element style rules of different parts of the style sheet. This is neither practical nor maintainable, so it makes sense to group media queries with declarations related to the same selector that has been changed.

CSS custom properties provide a link between logic and design implementation. This also means that in most cases, media queries should not be required other than changing CSS custom properties, which should be located at the top of documents with CSS custom property declarations. Above the “Logical Fold” .

Simplified selector

Effectively separating logic from design can also avoid the complexity of the main attribute declaration, allowing selectors to be combined.

In the following example, we have a <aside> and <main> elements with different font-sizes . At the same time, <aside> has a black background color, and <main> has a bright background color. So we can write it like this:

/* 默认值 */
:root {
--font-size: 1rem;
--background-color: #fff;
--color: #333;
}

/* aside元素内的值 */
aside {
--font-size: 1.2rem;
--background-color: #333;
--color: #fafafa;
}
/* 属性的声明 */
main,
aside {
font-size: var(--font-size);
color: var(--color);
background-color: var(--background-color);
}

Although they look completely different, these two elements have exactly the same property declarations.

More general CSS custom properties

You may have the idea of declaring all CSS custom properties related to processing logic in a generic selector * . In fact, this approach is not good:

/* 千万不要这样做 */
*{
display: var(--display);
width: var(--width);
height: var(--height);
border: var(--border);
background: var(--background);
...
}

While this is fun, we should pay more attention to reusing CSS custom properties and combinatorial selectors. CSS custom properties are affected by cascading. We can set the border in .container like this:

.container {
--border: 2px solid #ccc;
}

Everything inside the container will inherit --border . Soon, you will override all CSS custom properties and not need a generic * selector to get yourself into the pit.

Differences between CSS custom properties and CSS handler variables

On the surface, many colleagues think that CSS custom properties are somewhat similar to variables in CSS processors, but in fact there are still significant differences between them.

Grammatical differences

CSS custom properties are a bit like variables in CSS handlers, but there are still big differences. The most important and obvious difference is syntax. For example, in SCSS we use $ to declare variables, and we don't need to declare them in Code Block {} .

$color: red;

CSS custom properties are declared using the prefix -- and need to be declared within a selector block, for example:

:root {
--color: red;
}

Moreover, there are obvious differences in references. SCSS references declared variables using the syntax rule of “ Attribute — Value Pair “. Custom attributes need to be retrieved through the var () function.

// SCSS
body {
color: $color;
}
/* CSS自定义属性 */ 
body {
color: var(--color);
}

Another obvious difference is the name. They are called custom properties because they are pure CSS properties. In CSS processors, you can declare variables anywhere, including external declaration blocks, in media queries, and even in selectors, such as:

// SCSS
$breakpoint: 800px;
$color: #d33a2c;
$list: ".text, .cats";

@media screen and (min-width: $breakpoint) {
#{$list} {
color: $color;
}
}

Custom properties and regular CSS properties are used the same way. It is better to treat them as dynamic properties than variables. This means they can only be used in declaration blocks. In other words, custom properties and selectors are strongly bound. As mentioned earlier, it can be the : root selector or any valid CSS selector.

:root {
--color: red;
}

@media screen and (min-width: 800px) {
.text,
.cats {
color: var(--color);
}
}

You can get CSS custom declared values anywhere in the property declaration, which means they can be used as a single value, as part of a shorthand statement, or even in the calc () function:

.cats {
color: var(--color);
margin: 0 var(--margin-horizontal);
padding: calc(var(--margin-horizontal) / 2);
}

But CSS custom properties cannot be used for media queries or selectors, including structural selectors like : nth-child () :

/* 下面这样的用法是无效的 */
:root {
--num: 2;
--breakpoint: 30em;
}

div:nth-child(var(--num)) {
color: var(--color)
}
@media screen and (min-width: var(--breakpoint)) {
:root {
--color: green;
}
}

Dynamic vs. Static

The process of running the CSS processor mechanism may be roughly as follows:

The code in the CSS processor still needs to be presented to everyone on the page as CSS code . That is to say, variables or other functions in the CSS processor only take effect when compiling, which is a static . However, CSS custom properties are different. They are dynamic , and you can make corresponding changes when the Client runs. For example, the spacing of .card is different when running at different breakpoints.

In CSS processors, we may do it like this, let’s take SCSS as an example:

// SCSS
$gutter: 1em;

@media screen and (min-width: 30em) {
$gutter: 2em;
}
.card {
margin: $gutter;
}

Colleagues who have used CSS processors know that the $gutter in @media does not take effect, and in the final compiled CSS code, only:

.card {
margin: 1em;
}

No matter how the browser width changes, the value of $gutter is always 1em . This is called static (the processor cannot dynamically compile in the Client) . The main reason is that the CSS processor needs to be compiled before it can run in the Client, while CSS custom properties do not need to go through the compile process and can be used directly on the Client, for example:

:root {
--gutter: 1em;
}

@media screen and (min-width: 30em) {
:root {
--gutter: 2em;
}
}
.card {
margin: var(--gutter);
}

In the above example code, when you change the browser window, you will find that the margin of .card will change accordingly. That is, CSS custom properties are dynamic (can respond dynamically in the Client) .

In addition, we cannot use JavaScript to dynamically modify SCSS variables (the same goes for variables in other CSS processors). However, CSS custom properties are different. We can dynamically obtain or modify the values of CSS custom properties through the CSSOM API. We have also discussed the basic usage of CSS custom properties earlier. For example, let elements change position with mouse movement.

:root {
--mouse-x;
--mouse-y;
}

.move {
left: var(--mouse-x);
top: var(--mouse-y)
}
let moveEle = document.querySelector('.move');
let root = document.documentElement;

moveEle.addEventListener('mousemove', e => {
root.style.setProperty('--mouse-x', ${e.clientX}px);
root.style.setProperty('--mouse-y', ${e.clientY}px);
})

Cascade and inheritance

CSS processors and CSS custom properties have a significant difference, which is in terms of hierarchy and inheritance. Through the previous discussion, we know that CSS custom properties have the characteristics of cascading and inheritance related to CSS properties. However, this feature is not available in CSS processors. Let’s first look at the characteristics of cascading:

// SCSS
$font-size: 1em;

.user-setting-larger-text {
$font-size: 1.5em;
}
body {
font-size: $font-size;
}
/* CSS自定义属性 */ 
:root {
--font-size: 1em;
}

.user-setting-large-text {
--font-size: 1.5em;
}
body {
font-size: var(--font-size);
}

In the example above, the CSS code compiled by SCSS, the font-size of the body is always 1em , even if the user explicitly sets .user-setting-large-text . But it is different in CSS custom properties. The default font-size value of the body is 1em . Once user-setting-large-text takes effect (for example, explicitly adding this class name in the body or JavaScript adding this class name to the body ), the body 's font-size value becomes 1.5em .

Let’s look at another example of inheritance. Take the UI of an alert box as an example. Many times we want the UI of certain elements to inherit the value of the parent element or perform corresponding calculations on it, such as the following example:

// SCSS
$alert-color: red;
$alert-info-color: green;

.alert {
background-color: $alert-color;
&.info {
background-color: $alert-info-color;
}
button {
border-color: darken(background-color, 25%);
}
}
/* CSS 自定义属性 */ 
.alert {
--background-color: red;
background-color: var(--background-color)
}

.alert.info {
--background-color: green;
}
.alert button {
border-color: color-mod(var(--background-color), darken(25%));
}

Scope: Global vs. Local

Here is a simple starting point. Later, we will spend a section specifically introducing the scope of CSS custom properties.

In CSS processors such as SCSS, variables have two types: local ( local ) and global ( global ) . Any variable declared by a selector or constructor must be a global variable, otherwise it is a local variable.

Any hidden code block can access variables inside the enclosure.

$globalVar : 10px; // Global variable
.enclosing {
$enclosingVar: 20px; // Local variable
.closure {
$closureVar: 30px; // Local variable
font-size: $closureVar + $enclosingVar + $globalVar; // 60px
}
}

This also means that in SCSS, the scope of variables depends entirely on the structure of the code. However, CSS custom properties, like other CSS properties, have the property of inheritance.

Custom properties cannot have a global variable declared outside the selector — this is not valid CSS. The global scope of CSS custom properties is actually the : root scope, so the custom property declared by : root is a global variable.

Let’s use familiar SCSS syntax knowledge for HTML and CSS. We will create examples to demonstrate CSS custom properties. First, let’s take a look at the HTML section.

<!-- Global -->
<div class="enclosing">
Enclosing
<div class="closure">
closure
</div>
</div>

CSS styles are as follows:

:root {
--globalVar: 10px;
}
.enclosing {
--enclosingVar: 20px;
}

.enclosing .closure {
--closureVar: 30px;
font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar));
}

So far, we haven’t seen any difference between this and SCSS variables. Let’s redistribute the usage of variables. Let’s first look at the situation with SCSS, but it doesn’t work:

.closure {
$closureVar: 10px; // Local Variable
font-size: $closureVar + $enclosingVar + $globalVar;
$closureVar: 50px; // Local Varialbe
}

But in CSS, the computed value is recalculated by changing the value of --closureVar , thus changing the value of font-size :

.enclosing .closure {
--closureVar: 30px;
font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar);
--closureVar: 50px;
}

This is a big difference: if you reassign the value of a custom property, the browser will recalculate the variable used by the calc() expression .

The CSS processor does not know the DOM structure

Suppose we want to use the default font-size in block elements except for those with the highlighted class:

<!-- HTML -->
<div class="default">
default
</div>

<div class="default highlighted"> default highlighted </div>

We use CSS custom properties.

.highlighted {
--highlighted-size: 30px;
}
.default {
--default-size: 10px;
font-size: var(--highlighted-size, var(--default-size))
}

Because the second HTML plain uses the highlighted class name in addition to the default class name, the highlighted attribute will be applied to this element. In this example, it means that --highlightened-size: 30px will be applied, and --highlightened-size will be used on font-size .

Now we implement the same thing in CSS processors (such as SCSS).

// SCSS
.highlighted {
$highlighted-size: 30px;
}

.default {
$default-size: 10px;
@if variable-exists(highlighted-size) {
font-size: $highlighted-size;
}
@else {
font-size: $default-size;
}
}

In fact, $default-size is used for both elements, and $highlightened-size does not take effect. This is because the calculation and processing of SCSS occur at compile time, of course, it does not know anything about the DOM structure and depends entirely on the structure of the code. CSS custom properties are different, they have enclosing scope and cascading CSS properties related to the DOM structure.

Simply put, CSS custom properties can understand the structure and dynamic changes of the DOM .

Basic and logical operations

CSS processors have basic operations and logical operations, which can be similar to other programming languages. For example, in SCSS, four arithmetic operations ( + , - , * , / and % ), comparison operations (, < , = , < = ), equality operations ( == and ! = ), logical operations ( and , or and not ), and conditional judgments @if , @else and traversal @each , @for and other operations can be performed directly. However, in CSS custom properties, this aspect is weaker and can only be done with the help of calc () function.

.card {
--gap: 10;
padding: calc(var(--gap) * 1px) 0;
}

The @if and @else features in the CSS processor can help us perform some conditional judgment operations in the code. However, there are no features like @if and @else in CSS custom properties. However, we can use the relevant features of CSS custom properties with the calc() function to implement a conditional judgment function similar to if... else . Assuming there is a custom property --i , when:

  • --i with a value of 1 means true (i.e. open)
  • --i value of 0 means false (i.e. closed)

Let’s look at a small example, we have a container .box , and we want to make a conditional judgment based on the value of the custom property --i to be 0 or 1 :

  • When the value of --i is 1 , it means true, and the container .box rotates 30deg
  • When the value of --i is 0 , it means false, and the container .box does not rotate

The code may look like this:

:root {
--i: 0;
}

.box {
// 当 --i = 0 » calc(var(--i) * 30deg) = calc(0 * 30deg) = 0deg
// 当 --i = 1 » calc(var(--i) * 30deg) = calc(1 * 30deg) = 30deg
transform: rotate(calc(1 - var(--i)) * 30deg))
}
.box.rotate {
--i: 1;
}

Or:

:root {
--i: 1;
}

.box {
// 当 --i = 0 » calc((1 - var(--i)) * 30deg) = calc((1 - 0) * 30deg) = calc(1 * 30deg) = 30deg
// 当 --i = 0 » calc((1 - var(--i)) * 30deg) = calc((1 - 1) * 30deg) = calc(0 * 30deg) = 0deg transform: rotate(calc((1 - var(--i)) * 30deg))
}
.box.rotate {
--i: 0;
}

The whole effect is as follows:

The above demonstration is the switch between 0 and 1 . In fact, it can also switch between non-zero values. The switch between non-zero values is relatively more complex. I will not elaborate too much here. If interested, you can read @Ana's two blog posts:

In addition to the features mentioned above, some processors (such as SCSS) also have some function functions, which can help CSS processors better process code and provide more powerful features. For CSS custom properties, CSS function features can also be used to do some stronger things. For example, in the previous example, we have seen the figure of color-mod () :

.alert button {
border-color: color-mod(var(--background-color), darken(25%));
}

Scope of CSS custom properties

When a custom property is referenced by var() function, it is also called a variable . In programming languages we are familiar with, such as JavaScript, variables have the concept of scope. So CSS custom properties also have the concept of scope. Next, let's talk about the content related to CSS custom property scope.

The first variable in CSS currentColor

Before we talk about CSS custom property scopes, let’s talk about another variable in CSS, currentColor .

The earliest variable in CSS is currentColor , but it is not a property, but a CSS property value. Although currentColor is called a variable, there is a limitation to its use:

currentColor can only be used in places that accept <color> values; if a CSS property does not accept <color> values, it cannot accept currentColor as a value .

There are many properties in CSS that can accept <color> values, such as border-color , background-color , box-shadow , text-shadow , outline , and CSS gradual change properties. If we want the color of the element's border, shadow, background, etc. to be synchronized with color , then we can use currentColor .

The currentColor value is determined by the computed value of the color property used by the current element .

With the help of currentColor , we can better extend the color cascade. That is to say, when the value of color changes, the color property using currentColor will also change. For example:

Although currentColor and CSS custom properties are both called variables, there are still some differences between the two. I will explain the differences between the two through a simple case. Assuming we have a scenario like this, we hope that the border of the title and the color of the body text apply the same color , as shown in the following figure:

In fact, achieving the effect in the above picture is not difficult. What is more difficult is how to achieve it with the least amount of code and the simplest way, and also has the need for skin peeling. Assuming that the DOM structure we use to achieve the effect in the above picture is like this:

<!-- HTML -->
<body>
<h3>我是一个标题</h3>
<p>我是一段文本,文本的颜色和标题边框色是一样的...</p>
</body>

Before achieving the above effect, give yourself up the ante (plus some implementation restrictions):

You only want to set the color value in one place, and h3 's border-color inherits this color.

The purpose is to want the border color of the title to be the same as the color of the body text. At this time, you may think of the inheritance keyword inherit in CSS cascade.

body {
color: red;
}

h3 {
color: #000;
border: 3px double;
border-color: inherit;
}

But the final result is not what you expected.

Why is this happening? Let’s analyze it briefly together.

Although the value of color is explicitly set to red in the body , the value of border-color is not explicitly set. The rule of CSS is that if the value of the CSS property is not explicitly set, its initial value (default value) will be used . The default value of border-color is currentColor . That is, based on the context of this example, the value of currentColor is red , which is also the expected value to be inherited.

In that case, the inherited value is not red ? This is mainly because we explicitly set the value of color to #000 in CSS for h3 , and the value inherited from the parent element of border-color is currentColor . The value of currentColor is closely related to the value of color , so the final rendered border color is #000 . In this way, to solve this problem is very simple, just explicitly set the value of border-color to red on the body <--atag--26/> element:

body {
color: red;
border-color: red;
}

h3 {
color: #000;
border: 3px double;
border-color: inherit;
}

Now the value inherited from h3 is no longer the variable currentColor , but inherits the border-color of the body , that is, red . So we get the effect we want:

Using currentColor to achieve the effect we want, but the appearance of CSS custom properties, using CSS custom properties to handle this effect will be easier and more maintainable:

body {
--color: red;
color: var(--color);
border-color: var(--color);
}

h3 {
--color: #000;
border: 3px double;
border-color: inherit;
}

In this case, h3 's border-color inherits the custom property --color of the parent element body . However, even if h3 's color value is the local custom property --color (local variable), its border-color will not use this locally declared custom property value like currentColor .

The value of the inherited custom property setting always matches the value resolved by the parent property. The color property in this example will take the local value because it is not inherited.

The key difference between currentColor and CSS custom properties is:

The currentColor keyword is not parsed when the value is computed, but rather a reference to the usage value of the local color property .

Scope in CSS

When talking about the CSS variable currentColor , we mentioned the term local variable . However, the concept of scope in CSS is very vague. In order for everyone to better understand the scope of CSS custom properties, it is necessary for us to spend some time understanding the "scope" in CSS. In other words, how to better understand CSS scope? (This is a headache for many people).

@PPK has discussed CSS scope in his blog . Any selector in CSS is global (valid throughout the document). For example:

p {
color: red;
}

The above code will make the color value of all p elements in the entire document red . However, many times, it is always necessary to set different color values for different p elements in different places. At this time, you can only add other elements, class names or IDs in front of the p element, such as:

/* 文档所有p元素 */
p {
color: red;
}

/* 文档所有div中的p元素 */
div p {
color: blue;
}
/* 文档所有.blog中的p元素 */
.blog p {
color: green;
}
/* id为blog中的p元素 */
#blog p {
color: orange;
}

This is closely related to the structure of the document source code. If the document structure changes, your selector range will also change accordingly.

In addition, even if you add a module (range) in front of the p element, in addition to the constraints of its document structure, it is also limited by the constraints of CSS selector weights. Take the example below:

<!-- HTML -->
<div id="outer">
<p>我是什么颜色?</p>
<div id="inner">
<p>我是什么颜色?</p>
</div>
</div>
p {
color: red;
}

#inner p {
color: green;
}
#outer p {
color: orange;
}

Although range restrictions are added in front of the p element #inner p and #outer p , according to the selector weights and cascading characteristics of CSS, it can be known that the selector weights of #inner p and #outer p are the same. At this time, which selector is at the back has a higher chance of being used (the same weight is, and the later one will overwrite the previous one). Therefore, the text color of the p element in the final example will be orange . If you change the order of the two:

#outer p {
color: orange;
}

#inner p {
color: green;
}

At this point, the text color of the p element in the above example will be green . This is the headache CSS selector problem, also known as the scope problem.

Thankfully, with the advent of several JavaScript frameworks, there are many CSS-in-JS solutions in the community, one of whose main purposes is to use reasonable, readable syntax to limit the scope of CSS selectors .

However, the problem of CSS domain names seems to be solved in the near future. In other words:

Limited local scope (selector local scope) already exists in CSS.

Scope of CSS custom properties

CSS custom properties (also known as CSS variables) are already scoped. Custom property values can be redefined for specific elements and their descendants .

Before delving into CSS custom property scopes, let’s recall the scope in JavaScript and CSS processors (such as SCSS). As we all know, the scope of the variable var in JavaScript is related to the function , while the scope of variables declared by let and const is related to the block scope.

Take a look at an example of JavaScript closure:

// JavaScript
window.globalVal = 10;

function enclosing() {
var enclosingVar = 20
function closure() {
var closureVar = 30
return globalVal + enclosingVal + closureVar
}
return closure()
}

Closure () accesses variables in the outer (enclosing) function scope. Closure has three scopes, and its access is as follows:

  • Own scope scope (variables in {} )
  • Variables for external functions
  • Global variable

SCSS and JavaScript are a bit similar. Variables declared in any selector or constructor (such as the mixin macro @mixin ) are global variables, otherwise they are local variables. And any nested Code Block can access the variables inside the enclosing:

$globalVar: 10px; // Global variable

.enclosing {
$enclosingVar: 20px; // Local variable
.closure {
$closureVar: 30px; // Local variable
font-size: $closureVar + $enclosingVar + $globalVar
}
}

In the previous example, we demonstrated two ways to use CSS custom properties.

:root {
--color: red;
}

p {
color: var(--color);
}
#inner p {
--color: green;
color: var(--color);
}

When explicitly declaring CSS custom properties, the common way is shown in the above figure. One is in the :root {} selector block, and the other is in a non- :root {} selector (such as the #inner p {} selector in the above figure). The CSS custom properties declared in the :root selector act on the global scope (its scope is called global), not the CSS custom properties declared in the :root selector act on the selector block and its descendant elements (its scope is called local or local).

Many times everyone will equate :root with html , but in fact it is not the case, because the weight of html is greater than :root , just like the weight of div with class name is greater than the element tag div , for example:

:root {
--color: red;
}

html {
--color: green;
}
.class {
--color: orange;
}

In this way, its scope is:

  • --color in .class applies to .class elements and all descendant elements
  • --color in html applies to the <html> element and all descendant elements
  • --color in : root applies to <html> and all its descendants in HTML, and to <svg> and all its descendants in XML (e.g. svg )

Therefore, if you want to declare a global CSS custom property, the best way is to declare it in the :root selector. This is also why people prefer to declare custom properties in :root rather than declaring global CSS custom properties on html elements. In addition, declaring CSS custom properties in :root also helps to separate the CSS custom properties to be used later from the styled selectors being used for the document.

In this case, should all custom properties be declared in :root ? Before answering this question, let's go back to the way variables are declared in SCSS. In SCSS, variables declared outside all blocks (variables declared outside any selector) are global variables that can be used anywhere. This is useful because it makes it clear what you want to do.

If you think in the same way, all variables are declared in :root , for example:

:root {
--clr-light: #ededed;
--clr-dark: #333;
--clr-accent: #EFF;
--ff-heading: 'Roboto', sans-serif;
--ff-body: 'Merriweather', serif;
--fw-heading: 700;
--fw-body: 300;
--fs-h1: 5rem;
--fs-h2: 3.25rem;
--fs-h3: 2.75rem;
--fs-h4: 1.75rem;
--fs-body: 1.125rem;
--line-height: 1.55;
--font-color: var(--clr-light);
/* 可能只用于 navbar */
--navbar-bg-color: var(--clr-dark);
--navbar-logo-color: var(--clr-accent);
--navbar-border: thin var(--clr-accent) solid;
--navbar-font-size: .8rem;
/* 可能只用于header */
--header-color: var(--clr-accent);
--header-shadow: 2px 3px 4px rgba(200,200,0,.25);
--pullquote-border: 5px solid var(--clr-light);
--link-fg: var(--clr-dark);
--link-bg: var(--clr-light);
--link-fg-hover: var(--clr-dark);
--link-bg-hover: var(--clr-accent);
--transition: 250ms ease-out;
--shadow: 2px 5px 20px rgba(0, 0, 0, .2);
--gradient: linear-gradient(60deg, red, green, blue, yellow);
/* 可能只用于button */
--button-small: .75rem;
--button-default: 1rem;
--button-large: 1.5rem;
}

By declaring all CSS custom properties in :root , we can easily manage CSS custom properties and know where they are used. However, why do we need to declare CSS custom properties related to navbar , header , and button in :root ? In my understanding, they should be local and not used globally. Since they are not global properties, why explicitly declare them in :root ? This is where the local scope of CSS custom properties should come into play.

If you want to see an example, you can go back to the section “Application of CSS Custom Properties in Web Components”. When designing the Button component, we made good use of the concept and design concept of CSS custom property scope.

When using CSS custom properties, we can distinguish between global and local CSS custom properties by case. Because CSS custom properties are case-sensitive, it is recommended to use uppercase for global CSS custom properties and lowercase for local domain name CSS custom properties. For example:

:root {
--PRIMARY: #f36;
}

.button {
--button-primary: var(--PRIMARY);
}

CSS custom properties allow degrade values to be provided when variables are used by the var() function. Therefore, in actual use, we should avoid directly overriding the CSS custom properties of the global scope. The purpose of doing so is to avoid separating the CSS custom properties of the global scope from their values as much as possible. To achieve this requirement, degrade values can be used to achieve this. For example:

:root {
--THEME-COLOR: var(--user-theme-color, #f36);
}

body {
--user-theme-color: green;
background-color: var(--THEME-COLOR);
}

Setting global dynamic properties indirectly like this can prevent them from being overwritten locally and ensure that user settings are always inherited from the root element. This convention can avoid accidental inheritance of theme parameters.

CSS custom property application scenarios

Today, CSS has gained support from mainstream browsers. Moreover, CSS custom properties can help us change the way we think and build CSS. We can use CSS custom properties in many scenarios. Using CSS custom properties not only provides flexibility, but also allows me to better maintain my CSS code.

Give a few examples of using CSS custom properties.

Use CSS custom properties to change colors

There are many color modes in CSS, such as rgb , rgba , hsl , hsla , etc. Many times we use JavaScript to manipulate CSS colors. After the appearance of CSS custom properties, we can also use CSS custom properties to modify colors.

:root {
--COLOR-R;
--COLOR-G;
--COLOR-B;
--COLOR-A;
--THEME-COLOR: rgba(var(--COLOR-R), var(--COLOR-G), var(--COLOR-B), var(--COLOR-A));
}

CSS custom property skinning

Many times in web development, there may be a need for skin changing. Using CSS custom properties can make skin changing easier. For example, if we want to switch between highlight mode and dark mode:

Using CSS custom properties can also achieve more complex theme switching effects.

Control Streaming Layout with CSS Custom Properties

We have previously introduced the method of precise fluid layout in multiple articles.

  • Window-based typesetting
  • How to precisely control responsive typography
  • Achieve accurate fluid typesetting principles

Usually we use the window unit vw and calc() function to calculate font-size or padding :

If we use CSS custom properties, we can replace the values in the above formula with CSS custom properties to achieve the effect of streaming layout.

Application of CSS custom properties in web components

Simply put, how to use CSS custom properties to better provide API-like interfaces for web design, allowing users to better use web components and more flexibly extend and override the UI style of components.

Application of CSS custom properties in Web Animation

Using CSS custom properties can be well combined with Web Animation.

It can also be flexibly combined with SVG motion graphics to achieve some micro motion graphics effects.

Responsive motion graphics can also be achieved by combining the mouse or gestures.

Other demos

There are many demos about CSS custom properties on Codepen . If you are interested, you can check it out above.

Summary

In this article, we have spent a long time discussing the knowledge related to CSS custom properties with everyone. So far, mainstream browsers have good support for CSS custom properties, and we can boldly start using them. If you are worried about compatibility issues, you can consider using PostCSS-related plugins (postcss-custom-properties) to downgrade CSS custom properties.

This article provides an introduction to CSS custom properties, which is not the most comprehensive but covers the most content. I hope it will be helpful to you. If you have any good suggestions or related experiences in this area, please feel free to share them with us in the comments below.

Extended reading:

--

--

w3cplus

Author of "Modern CSS," "Modern Web Layout," "In-Depth CSS Defense," and "A Journey into Web Animation!"