Introduction

Custom properties (also known as CSS variables) can be a really useful and powerful tool when writing our CSS files. In short, they allow us to reference a CSS value however many times we want throughout a file. By using custom properties, instead of having to update every single instance of a specific value (“This shade of red is too light, let’s change the shade on all seven of these selectors”), we only need to update a single instance: the custom property itself. Not only that, but custom properties can help us keep colors consistent throughout a project, something that will be really helpful as projects get larger.

We can even redefine custom properties under different contexts, which is incredibly useful for creating themes, such as the dark and light themes you see on many websites these days.

Learning Outcomes

  • You’ll learn how to declare a custom property
  • You’ll learn how to access a custom property in a rule declaration

Using Custom Properties

The syntax for declaring and accessing a custom property is really simple and not too different from how we write normal rule declarations:

.error-modal {
  --color-error-text: red;
  --modal-border: 1px solid black;
  --modal-font-size: calc(2rem + 5vw);

  color: var(--color-error-text);
  border: var(--modal-border);
  font-size: var(--modal-font-size);
}

That’s it! First, we declare our custom property with a double hyphen followed by a case-sensitive, hyphen-separated property name (color-error-text wouldn’t be the same as Color-Error-Text). The use of single hyphens to separate words is very important here because spaces are not valid (--color error text would not work). Then we can store any valid CSS value in our newly declared custom property, whether it be a simple color value, shorthand values, or even a more complex function, just to give you a few examples.

When we want to access a custom property, we use the var() function as the value of a CSS property, and then place our custom property inside of the parenthesis (including the double hyphen at the beginning).

Fallback Values

The var() function actually accepts two parameters. The first parameter we’ve already gone over, which is the custom property we want to assign. The second parameter is an optional fallback value. When a fallback value is provided in addition to a custom property, the fallback value will be used if the custom property is invalid or hasn’t been declared yet. We can even pass in another custom property as a fallback, which can have its own fallback value as well!

.fallback {
  --color-text: white;

  background-color: var(--undeclared-property, black);
  color: var(--undeclared-again, var(--color-text, yellow));
}

In the above example, our background-color property would have a value of black and our color property would have a value of white. If the --color-text custom property was invalid or didn’t exist, the fallback to our fallback would take over and the color property would have a value of yellow.

Scope

In the first example above, you may have noticed that we declared and then accessed our custom properties within the same declaration block. That’s because the scope of a custom property is determined by the selector. This scope includes the selector the custom property was declared for as well as any descendants of that selector. If you’re familiar with how scope works in JavaScript, this sort of behavior should feel a little similar.

In the example below, only the element with the cool-paragraph class would get styled with a red background since it’s a descendant of the element where our custom property is declared.

<div class='cool-div'>
  <p class='cool-paragraph'>Check out my cool, red background!</p>
</div>

<p class='boring-paragraph'>I'm not in scope so I'm not cool.</p>
.cool-div {
  --main-bg: red;
}

.cool-paragraph {
  background-color: var(--main-bg);
}

.boring-paragraph {
  background-color: var(--main-bg);
}

The :root Selector

While there may be times where you will want to limit the scope of a custom property, you may want to be able to use other custom properties on many, unrelated selectors. One workaround would be declaring the same custom property on a bunch of selectors, but that defeats one of the purposes of using custom properties in the first place (the ease of changing multiple instances of a value at once).

A better solution is declaring those custom properties on the :root selector, which is essentially the same thing as the html selector except it has a higher specificity.

<p class='cool-paragraph'>Lorem ipsum dolor sit amet.</p>

<p class='exciting-paragraph'>Lorem ipsum dolor sit amet.</p>
:root {
  --main-color: red;
}

.cool-paragraph {
  color: var(--main-color);
}

.exciting-paragraph {
  background-color: var(--main-color);
}

By declaring our custom property on the :root selector in the example above, we can access it on any other valid selector within our CSS file, since any other selector would be considered a descendant of the :root selector.

Creating Themes with Custom Properties

Beyond allowing us to access custom properties more globally, the :root selector gives us one way to add themes to our pages:

See the Pen Theme Toggle | CSS Custom Properties by TheOdinProject (@TheOdinProjectExamples) on CodePen.

First we added a class attribute on our html element so that our page has a default theme. Next in our CSS we created two scopes for our custom properties on the :root selector, one for when our html (or root) element has a class of dark and another for when it has a class of light. Our other selectors then use the values of any custom properties depending on which class is currently present on our root element.

Media Queries

Giving users the ability to toggle a theme themselves is great, but there’s another option for setting a theme that you may have come across on certain sites or applications: using the user’s theme setting from their operating system or user agent (like a browser). This can be accomplished with the prefers-color-scheme media query, which simply checks whether a user has selected a theme preference on their OS/user agent. As you view the example below, try changing the theme settings on your OS/user agent to see how the example updates in real time!

See the Pen Theme Query | CSS Custom Properties by TheOdinProject (@TheOdinProjectExamples) on CodePen.

We first added custom properties on the :root element outside of the media query. This gives us a default theme in case a user doesn’t have a preference set on their OS or user agent, or if a browser doesn’t support the media query. In this case, we’re using our “light” theme colors as the default. Then we added a prefers-color-scheme media query for when a user has a dark theme set in their preferences.

Using the prefers-color-scheme media query can be pretty helpful for users since it doesn’t require them to manually change the theme to their preferred one. That said, you need to be aware of a few things when it comes to using this media query:

  1. Only dark and light are valid values for the media query, so you can’t use it to implement any themes beyond these two basic ones.
  2. The light value for the media query is actually for when a user has a light theme specified or when they have no preference set.
  3. It doesn’t allow users to change the theme themselves, which can still be important in cases where a user might want to use the theme opposite of their OS/user agent preferred one for whatever reason.

Assignment

  1. Read through MDN’s Using CSS custom properties page starting at the “Inheritance of custom properties” section.
  2. Open the inspector on this page to inspect the styles and see how Odin uses some custom properties.

Additional Resources

  1. This video on CSS custom properties is a great introduction. Go ahead and watch it.

Knowledge Check

This section contains questions for you to check your understanding of this lesson. If you’re having trouble answering the questions below on your own, review the material above to find the answer.

Improve this lesson on GitHub

Have a question?

Chat with our friendly Odin community in our Discord chatrooms!

Open Discord

Are you interested in accelerating your web development learning experience?

Get started
Thinkful Logo

Thinkful

  • 5-6 months

    5-6 months

  • Job Guarantee

    Job Guarantee

  • 1-on-1 Mentorship

    1-on-1 Mentorship