Lesson 14: Custom Properties

Say you have been building some early styles for your site, and you find yourself repeating the same values in your CSS. You are using dodgerblue as your primary color. You add that color to button borders, link text, and header backgrounds. You even use a design tool to pick a few shades of that blue for other parts of the site. Then you get a style guide, and the primary color is now oklch(70% 0.15 270). Now you have to find and change that color everywhere.

Custom properties, also called CSS variables, let you organize and reuse values in your CSS. This makes your styles more flexible and easier to understand.

Creating properties

The simplest way to create a property is to set a value on a new property with a name you choose.

.card {
  --base-size: 1em;
}

All property names must start with two dashes. This stops you from using an existing CSS property name for a custom value. The CSS spec will never add a property that starts with two dashes.

You can then read this property with the var() function. This example sets the font size inside a .card-title to double the --base-size value.

.card .card-title {
  font-size: calc(2 * var(--base-size));
}

[!NOTE] It is a common pattern to set up properties on the root element, using the :root selector. This makes the default values available anywhere on the page, but you can still override them.

Using a custom property

As you have seen, you can use the value of a custom property with the var() function. You can use var() in values, but not in media queries. Custom properties are most useful as arguments to other CSS functions.

Fallbacks

What happens if you try to use a custom property that has no value set? The var() function takes a second value that it uses as a fallback. The fallback value can even be another custom property with a nested var().

#my-element {
  background: var(
    --alert-variant-background,
    var(--alert-primary-background)
  );
}

Invalid values

A custom property can resolve to a value that does not make sense for a property. An example is a value of 1em for the background-color property. When this happens, other valid declarations on that element for that property are not used. This is because the browser can’t tell that a value is invalid until after it has thrown away the other declarations while working out a value. Instead, the used value will be an inherited or initial value.

.content {
  background-color: blue;
}

.content.invalid {
  --length: 2rem;
  background-color: var(--length);
}

In the example above, the .invalid element won’t have a blue background. Instead, because background-color does not inherit, the value will be transparent, which is its initial value.

Overriding and inheritance

Most of the time you will want the default behavior of custom properties, which is that values inherit. When you set a new value for a property, that element and all of its children get that value, until something else overrides it.

Custom properties follow the cascade, so a more specific selector can also override them.

More control with @property

A custom property that you create by setting a value can be any type, and it inherits. For more control over a custom property, you can use the @property rule.

The --base-size property we made earlier would be the same as this @property declaration.

@property --base-size {
  syntax: "*";
  inherits: true;
  initial-value: 18px;
}

The syntax value sets the types of CSS values that are valid for the property. If you set a different type on that property, it will be invalid. It will then fall back to the initial value, or to an inherited value set higher in the cascade.

When you create a custom property using @property, you can turn off inheritance with inherit: false. Overriding the value for a custom property with inheritance turned off changes it for the selected element, but not for its children. This is often useful when more than one selector targets the same element.

The initial-value sets the value of the property, unless it is changed later. Unless the syntax is *, which means any CSS type, the @property must set an initial-value. This makes sure the property always has a value of the right syntax, and is never undefined.

Updating custom properties with JavaScript

You can update the value of a custom property on an element using JavaScript. You can use this to change your site’s styles on the fly.

const element = document.getElementById("my-button");
getComputedStyle(element).setPropertyValue("--color", orange);

This example updates the style tag on the #my-button element. If you inspect it in DevTools, you will see this.

<button id="my-button" style="--color: orange">Click me</button>

In the example above, you can see how to set custom properties by reading data stored in custom HTML attributes. Each button has a data-color attribute with the value of a specific color. The --background custom property set on the body element is reset to the value of data-color on whichever button you click.

You can also use getComputedStyle(element).getPropertyValue("--variable") to get a property’s value on a specific element. This can be useful when your logic needs to respond to a cascaded value.

Resources


Adapted from Learn CSS © Google and contributors, licensed under CC BY 4.0 (prose) and Apache 2.0 (code samples).