CSS variables, now enjoying fairly good browser support, are opening up exciting possibilities for more dynamic styling. Having true CSS variables means that we can get and set their values in JavaScript, allowing us to build cool features like live theming.

The process is fairly simple — setup initial variable values in CSS, markup some inputs, add event listeners and set the values in JavaScript. Let’s get started!

Live theme switcher using CSS variables, or custom properties. Allows custom control of colors and typography, or selection of predefined themes. Green version.
What we’re building: a live theming interface that saves values to localStorage.

Setup Initial Values

First we’ll set up the variables and their initial values in the :root pseudo-selector:

:root {
    --background-color: #2eec96;
    --text-color: #222;
    --label-color: #1a905d;
    --border-color: rgba(250, 250, 250, 0.5);
    --base-font-size: 14px;
    --base-line-height: 1.6;
}

Write some styles using these variables. Think about using these values as a base and generating many of the other values in your styles from them. For example, set your body to use the --base-font-size variable and set all other font sizes in rems or ems. This will let you create fewer variables while still giving the user the power to affect the entire document.

body {
    font-size: var(--base-font-size);
    line-height: var(--base-line-height);
    color: var(--text-color);
    background-color: var(--background-color);
}

You may also want to add some transitions for the specific properties that will be changing to smooth out the theme editing experience.

Create Some Inputs

Now it’s time to create the form the user will be interacting with to customize their theme. I’m using color inputs for the color options and range inputs for the font size and line height.

<input type="color" id="background-color" value="#2eec96">

<input type="range" id="base-font-size" min="12" max="16" step="1" value="14">

Set your initial input values to match the starting values of your CSS variables. You could also set these through JavaScript on page load to remain DRY.

Set up Event Listeners

I’m setting up listeners to the “change” event for color inputs, and the “input” event for range inputs. I’ve extracted the logic to a separate function called handleInputChange so I can reuse it for all the inputs. A small complication is the need to set --base-font-size in pixels and everything else in unitless values. So handleInputChange accepts a second parameter that is a simple Boolean to handle this.

const handleInputChange = (property, pixels) => {
    document.documentElement.style.setProperty(
        `--${property}`, 
        `${event.target.value}${pixels ? 'px' : ''}`
    );
};

document.querySelector('#background-color')
    .addEventListener('change', event => {
        handleInputChange('background-color', false);
    });

document.querySelector('#base-font-size')
    .addEventListener('input', event => {
        handleInputChange('base-font-size', true);
    });

That’s all it takes to set up basic interactive theming with CSS variables. We can go a step further and keep these values in localStorage so the theme settings will persist on refresh.

Live theme switcher using CSS variables, or custom properties. Allows custom control of colors and typography, or selection of predefined themes. Blue version.

Store Values in localStorage

I’ll edit my handleInputChange function to store values in localStorage:

const handleInputChange = (property, pixels) => {
    document.documentElement.style.setProperty(
        `--${property}`, 
        `${event.target.value}${pixels ? 'px' : ''}`
    );
    localStorage.setItem(
        property, 
        `${event.target.value}${pixels ? 'px' : ''}`
    );
};

Next, I’ll need to add some functionality to grab the values from localStorage and set them on page load. I will need to set the CSS variable and the input value so everything is consistent. Once again, I’ve extracted the logic to a separate function. And again, be careful of pixel values. This time, when setting the input values, the ‘px’ string will need to be removed.

const setValueFromLocalStorage = property => {
    let value = localStorage.getItem(property);
    document.documentElement.style.setProperty(`--${property}`, value);

    const input = document.querySelector(`#${property}`);
    input.value = value.replace('px', '');
}

document.addEventListener('DOMContentLoaded', () => {
    setValueFromLocalStorage('background-color');
});

Now, refresh your page to see a persistent theme!

Bonus: Pre-Defined Themes

If you want to offer some pre-defined themes, in addition to the free-form inputs, simply create a method that sets all the variables to pre-defined values and hook it up to an event listener.

I’ve created a setTheme function that accepts an options object, loops through the object keys, and sets the values. I’ve reworked the function that actually sets the values a bit so it’s more generic.

const setTheme = options => {
    for(let option of Object.keys(options)) {
        const property = option;
        const value = options[option];

        setValue(property, value);
        localStorage.setItem(property, value);
    }
}

Now, I just need to call this function with the values I want to set on the appropriate trigger:

setTheme({
    'background-color': '#2eec96',
    'text-color': '#222222',
    'label-color': '#1a905d',
    'border-color': 'rgba(250, 250, 250, 0.5)'
});
Live theme switcher using CSS variables, or custom properties. Allows custom control of colors and typography, or selection of predefined themes. Black version.

Conclusion

CSS variables make live theming a piece of cake. Check out the live experiment here. In a real-world app, you would probably want to store the theme options in user settings on the back-end. Hopefully this gets you started playing around with the fun things that CSS variables make possible.