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 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!
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.
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!
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!
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!
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!
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 to20
, 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!
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.