How to Create CSS Custom Properties That Dynamically Update with React & JavaScript

CSS variables have been around with tools like Sass, but only recently have they become native to CSS. Now that we have them available right in our browsers, how can we use JavaScript and tools like React to dynamically update the values?

What's Inside 🧐

What are CSS Custom Properties?

tl;dr they’re variables!

They’re the CSS Spec’s way of providing a variable-like capability native to the browser.

The basic syntax include setting a variable name with two hyphens (--) as a prefix such as:

--my-favorite-color: blueviolet;

In order for this to work though, you need to give that custom property “scope” by setting it on a selector, such as:

.my-element {
  --my-favorite-color: blueviolet;
}

Now in the case of the above, that property is only available on .my-element, so instead, you’ll typically see those properties set on the root instead so it can be used globally, such as:

:root {
  --my-favorite-color: blueviolet;
}

Once you make that property available though, you can use the var() function within a CSS definition to make use of that value, such as:

:root {
  --my-favorite-color: blueviolet;
}

.my-element {
  background-color: var(--my-favorite-color);
}

This is the basic gist, but what makes CSS custom properties powerful is you can also use JavaScript to both get and modify those values on the fly.

Interacting with CSS Custom Properties in JavaScript

By defining variables as custom properties, we now have access to those values in JavaScript.

If we take our earlier examples, we can easily get the value of --my-favorite-color by using:

getComputedStyle(document.documentElement).getPropertyValue('--my-favorite-color')

In the above, we’re passing the document’s documentElement object, which is the :root where we defined our property, to getComputedStyle. This allows us to have access to the styles of that node where we can then get the value of our custom property.

Further, if we wanted to then set that value, we can run:

document.documentElement.style.setProperty('--my-favorite-color', 'magenta');

Where we’re still accessing our document’s documentElement, but this time we’re stating that we want to set a style property of our property’s name to a new value.

While getting and setting are simple examples, it shows that we can now unlock new possibilities for making more interactive and dynamic experiences with CSS and JavaScript.

What are we going to build?

To see how this actually works in practice, we’re going to start up a simply React application using Create React App.

In our app, we’ll first see how we can get and set the values like we did in the above walkthrough, but we’ll take that a step further, where we’ll set those values dynamically based off of user interactions.

Step 0: Creating a new React app with Create React App

We’re going to start off with a new React app using Create React App.

Inside of your terminal, run:

yarn create react-app my-custom-properties
# or
npx create-react-app my-custom-properties

Note: feel free to use a different value than my-custom-properties as your project name!

Once installation has finished, you can navigate to that directory and start up your development server:

cd my-custom-properties

yarn start
# or
npm run start

And once loaded, Create React App should automatically launch the project in your browser, but if not, you should now be able to open up your new app at http://localhost:3000!

Browser showing new React app using Create React App
New React app using Create React App

Now before we move on, we’re first going to make a quick change in our app.

Currently we’re using an img tag to add an SVG file, which prevents us from updating the color with CSs. So to stat, let’s add our SVG logo inline.

You can do this in a few different ways:

  • Open the SVG file in your browser, view source, copy the contents, and replace the img tag
  • Open the SVG file in your app project, copy the contents, and replace the img tag

Or you can use this long snippet:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Note: Optionally you can simply style the Learn React link if you don’t want to go through the trouble of changing the element, but updating the color of the logo is a bit more fun! 🙂

Next, we want to add back the className attribute so we can still continue to style our logo, so on our svg element add:

<svg className="App-logo"

At this point, if you refresh your page, it should look like we haven’t made any changes yet, but now we’re ready to get started with Custom Properties.

Follow along with the commit!

Step 1: Using CSS Custom Properties to set colors

To get started with Custom Properties,let’s set the color of our React logo that’s spinning around in our app.

Now as we talked about earlier, before we can use a CSS Custom Property, we need to first define that value. More often than not it makes sense to do this in a global location so you can use that property anywhere you want.

In Create React App, the src/index.css file is loaded globally, so let’s open that file and add our first Custom Property definition.

At the top of src/index.css add:

:root {
  --color-logo: #61dafb;
}

We’re defining the same color that the logo is currently set to, but we’re adding it to a variable scoped to the :root of the document.

Next, inside of src/App.css, let’s update our logo styles to use that variable.

Because we’re targeting our inline SVG element, we need to target where the color is being set, so inside src/App.css add:

.App-logo g {
  fill: var(--color-logo);
}

Still yet, at this point, we shouldn’t see any difference, but now let’s try to update that color.

Back inside src/index.css update --color-logo to another color such as:

:root {
  --color-logo: orange;
}

We can now see our logo immediately changed to orange!

Create React App with orange logo
Updated React logo color to orange

Follow along with the commit!

Step 2: Getting CSS Custom Property values in JavaScript

Now that we have at least one color being set as a Custom Property, we can now see how we can get that value.

To start off, whenever our React application loads, we can immediately see what that value is.

Typically clientside code outside of the rendering lifecycle of a React component happens inside of a useEffect instance so to start, let’s import useEffect.

At the top of src/App.js add:

import { useEffect } from 'react';

Next, we can create a new instance of our useEffect hook:

useEffect(() => {

}, [])

And inside, we can run our code to find out what the current color of our logo is:

useEffect(() => {
  const color = getComputedStyle(document.documentElement).getPropertyValue('--color-logo');
  console.log(`--color-logo: ${color}`);
}, [])

When our page reloads, if we look inside of our browser console, we can see that value!

Highlighted color value in browser web console
Web console showing React logo color

Follow along with the commit!

Step 3: Setting the value of a CSS Custom Property with JavaScript

Not only do we want to get our Custom Property value, we want to set it.

To do this, we need some kind of event to occur, so how about we add buttons that change the color on click.

Starting off, let’s add our buttons. Inside src/App.js below the logo add:

<p>
  <button onClick={() => setColor('blueviolet')}>blueviolet</button>
  <button onClick={() => setColor('red')}>red</button>
</p>

In the above, I’m defining a new paragraph where I have two buttons. Those buttons each have an onClick handler that fires a function any time it’s clicked, passing in the name of a color.

Next, we need to define that setColor function, so somewhere before the return statement, add the function:

function setColor(color) {
  console.log(`Updating --color-logo to: ${color}`);
}

We’re creating our setColor function and currently just logging the value of the color argument passed in.

Next, let’s use that color value to actually update our Custom Property.

Update the setColor function to:

function setColor(color) {
  console.log(`Updating --color-logo to: ${color}`);
  document.documentElement.style.setProperty('--color-logo', color);
}

Similar to what we saw in the beginning of this article, we’re finding the document’s documentElement object and setting the style property to the color we choose.

Now if we open up our app, and click our buttons, we should now see the color immediately change!

Blueviolet React logo showing values in web console on click
Updating Custom Property color on button click

Follow along with the commit!

Step 4: Updating a CSS Custom Property dynamically on React input change

The cool thing about our Custom Properties is we can really take advantage of any type of event or trigger to update our values.

For instance, what if we wanted to dynamically change the size based off of an on-screen input?

To test this out, let’s first define a new Custom Property.

Inside src/index.css on the :root scope add:

--size-logo: 40vmin;

Then in src/App.css replace the 40vmin value on the .App-logo height to use that Custom Property:

.App-logo {
  height: var(--size-logo);

Like usual, you shouldn’t see any changes yet, so let’s fix that.

Inside of the app, let’s add an input range that will allow us to dynamically resize our logo.

First inside src/App.js, above the logo svg element, add:

<p>
  <input type="range" name="size" min="0" max="100" defaultValue="40" onChange={handleOnSizeChange} />
</p>

On our input, we’re using the onChange handler to fire a function any time the value changes, so let’s define that function.

Below setColor, add:

function handleOnSizeChange(event) {
  const value = event.currentTarget.value;
  document.documentElement.style.setProperty('--size-logo', `${value}vmin`);
}

Here we’re creating our handleOnSizeChange function where the first thing we do is grab the value of our input range.

With that value, we set our style propety of --size-logo based on that size value while using interpolation to add the postfix of vmin.

Now if you open up your browser, you should see that input range slider, and if you move it around, you should see the React logo immediately resize!

Using range slider input to resize React logo on the fly
Resizing React logo based on input change

Follow along with the commit!

Step 5: Changing animations based on CSS Custom Property values

Finally, we can even use Custom Properties to adjust animations on the fly when they’re defined with CSS.

For instance, inside src/App.css we’re setting an animation property on .App-logo that defines the rotation of our logo. If we wanted to speed that rotation up or slow it down, we can adjust the 20s value that defines how long the animation takes.

To start, let’s add a new Custom Property.

Inside src/index.css on the :root scope add:

--timing-logo: 20s;

Next, let’s update our animation to use that value:

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite var(--timing-logo) linear;
  }
}

As usual, at this point nothing should have changed, but now speed things up and slow them down any time someone clicks the logo.

Now adding click handlers on SVG elements is tricky, so instead of doing that, let’s wrap our SVG element with a paragraph tag and add an event handler for anytime someone clicks our element:

<p onClick={setTiming}>
  <svg className="App-logo" ...
</p>

We can then define that setTiming function under our other functions:

function setTiming() {
  const timing = getComputedStyle(document.documentElement).getPropertyValue('--timing-logo').replace('s', '');
  let newTiming = timing;

  if ( timing < .5 ) {
    newTiming = 20;
  } else {
    newTiming = newTiming / 2;
  }

  document.documentElement.style.setProperty('--timing-logo', `${newTiming}s`);
}

In our setTiming function, we’re:

  • First grabbing the current value of our timing Custom Property and stripping the s (seconds) so that we only have a number
  • Setting that value to a new variable that we’ll use to make the change
  • If our timing ends up below .5 seconds, we reset it to 20, otherwise, we split the time in half
  • Finally, we use that value to update our timing Custom Property

Now if we open up our browser and click on the React logo a few times, we should see it starting to spin faster and faster!

React logo spinning and speeding up when clicking on it
Clicking on React logo to speed it up

Follow along with the commit!

What else can we do?

Given we can use Custom Properties throughout our CSS, we have a lot of options as to what we can do.

Create themes for your website

Because we can define our variables dynamically, we can set our values to those Custom Properties and dynamically change the value or scope any time we want to flip to a different theme, such as light and dark mode.

Intelligently optimize for responsive design

Because you can now communicate between your CSS values and JavaScript, you can provide any updates needed that might not be possible with CSS or updates that need to coordinate with specific JavaScript functionality.