Home / Blog / How to Update :root CSS Variable with Javascript
How to Update :root CSS Variable with Javascript

How to Update :root CSS Variable with Javascript

Mostafa Said
Mostafa Said
Updated: December 21st 2024

CSS variables, also known as custom properties, have become a staple for web developers looking to create dynamic and maintainable styles. But what happens when you need to change these CSS variables at runtime? That’s where JavaScript comes in.

In this article, we’ll explore how to update CSS variables with JavaScript, using Vue.js as our framework of choice. We'll walk through the concepts and practical steps, providing code snippets to help you apply this to your own projects. By the end, you'll understand how to integrate CSS variables into your Vue components for a seamless, dynamic experience.

Why Use CSS Variables?

CSS variables are essential for writing DRY (Don’t Repeat Yourself) code, especially in complex applications. They allow you to define a value once and reuse it throughout your stylesheets. Whether you're dealing with theme switching, responsive design, or dynamic styling based on user input, CSS variables can handle it.

Here’s a quick example of how CSS variables look:

:root {
  --main-color: #42b983;
  --text-size: 16px;
}

body {
  background-color: var(--main-color);
  font-size: var(--text-size);
}

In this case, --main-color and --text-size are defined at the root level, making them accessible throughout the entire stylesheet. Now, what if you want to change --main-color dynamically, based on user interactions or application state? That’s where JavaScript comes in.

Accessing and Updating CSS Variables with JavaScript

JavaScript provides a powerful way to manipulate CSS variables at runtime by interfacing with the DOM's style property. The document.documentElement (or :root in CSS) is key here, as it allows you to access and modify global CSS variables. The getComputedStyle method retrieves the current value of a CSS variable, while setProperty updates it.

Here’s how you can access and update a CSS variable using JavaScript:

// Get the root element
const root = document.documentElement;

// Retrieve the current value of a CSS variable
const currentColor = getComputedStyle(root).getPropertyValue('--main-color');

// Update the CSS variable
root.style.setProperty('--main-color', '#ff5733');

This basic approach works in any JavaScript application. But how do we incorporate this into a Vue.js application for a more reactive and maintainable solution?

Updating CSS Variables in Vue.js

Vue.js is all about reactivity, making it a perfect fit for dynamically updating CSS variables. In Vue, you can easily integrate this logic within the Vue component lifecycle or bind it to a computed property for seamless updates.

Here’s an example that demonstrates how to update a CSS variable within a Vue component:

<template>
  <div>
    <button @click="changeColor">Change Theme Color</button>
  </div>
</template>

<script setup>
const changeColor = () => {
  const root = document.documentElement
  root.style.setProperty('--main-color', '#ff5733')
}
</script>

<style>
:root {
  --main-color: #42b983;
}

body {
  background-color: var(--main-color);
}
</style>

In this example, clicking the button triggers the changeColor method, which updates the --main-color CSS variable.

A gif showing after changing the CSS variable

Change CSS Variable in Vue.js at Runtime

The background color of the page will change accordingly.

Using Vue's watch for Reactive Updates

To take things further, you can use Vue.js’ watch API to reactively update CSS variables based on data changes. This is particularly useful when your app's state should control certain styles dynamically, such as in a theme switcher.

Let’s consider we have --main-color CSS variable in the :root and we’re using it to set the body color:

:root {
  --main-color: #42b983;
}

body {
  background-color: var(--main-color);
}

Here’s an enhanced version using Vue.js composition API with script setup and watch:

<template>
  <div>
    <button @click="toggleTheme">Toggle Theme</button>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const root = document.documentElement
const darkThemeHex = '#333'
const lightThemeHex = '#42b983'

const isDarkTheme = ref(false)

watch(isDarkTheme, (oldValue, newValue) => {
  const newColor = newValue ? lightThemeHex : darkThemeHex
  root.style.setProperty('--main-color', newColor)
})

const toggleTheme = () => {
  isDarkTheme.value = !isDarkTheme.value
}
</script>

Now, every time the isDarkTheme variable changes, the watch function is triggered, updating the --main-color CSS variable accordingly. This pattern is particularly useful in scenarios where themes or other styles need to change dynamically based on application state.

Practical Use Cases: Theme Switching and Responsive Design

Now that you understand the basics, let's explore some practical use cases where updating CSS variables with JavaScript and Vue.js can significantly improve your application.

1. Theme Switching

As demonstrated earlier, dynamically changing CSS variables is perfect for theme switching. You can store multiple themes as sets of CSS variables, and update them based on user preference. For example, you could switch between light and dark themes, or even offer more granular control over font sizes or color palettes.

2. Responsive Design Adjustments

Another great use of CSS variables is in responsive design. You can update layout styles based on screen size or device orientation. For instance, changing the spacing or font size for mobile users can be easily achieved by updating CSS variables based on a resize event.

window.addEventListener('resize', () => {
  const root = document.documentElement;
  const newFontSize = window.innerWidth < 600 ? '14px' : '16px';
  root.style.setProperty('--text-size', newFontSize);
});

In this code snippet, the font size adjusts dynamically based on the screen width, ensuring a responsive and user-friendly design.

Performance Considerations

While updating CSS variables with JavaScript is powerful, it's essential to be mindful of performance. Excessive DOM manipulation can cause layout reflows, especially in large applications. Vue’s reactivity system helps mitigate some of these concerns by ensuring updates are batched and efficient. However, it’s good practice to minimize the number of times you update CSS variables, particularly in scenarios where the changes are triggered by frequently occurring events like scrolling or resizing.

Best Practices:

Let’s go through some of the best practices to avoid any negative impact on performance.

1. Batch Updates

When updating multiple CSS variables, grouping them together in a single operation reduces browser reflows and improves performance.

Here’s an example using the Composition API with script setup to batch CSS variable updates:

<template>
  <div>
    <button @click="updateVariables">Update Variables</button>
    <p>Hello there!</p>
  </div>
</template>

<script setup>
const updateVariables = () => {
  const root = document.documentElement

  // Batch update using cssText
  root.style.cssText = `
    --main-color: #ff5733;
    --text-size: 18px;
    --border-radius: 10px;
  `
}
</script>

<style>
:root {
  --main-color: #42b983;
  --text-size: 14px;
}

body {
  background-color: var(--main-color);
  font-size: var(--text-size);
}
</style>

In this example, the cssText property batches multiple updates to minimize reflows.

2. Debouncing Updates

When updating CSS variables in response to frequent events like resizing, debounce the updates to improve performance.

Here’s how to debounce a resize event using the Composition API:

<template>
  <p>Resize the window to see the text size change</p>
</template>

<script setup>
import { onMounted, onBeforeUnmount } from 'vue'

let debounceTimeout = null

const updateTextSize = () => {
  const newFontSize = window.innerWidth < 600 ? '14px' : '16px'
  document.documentElement.style.setProperty('--text-size', newFontSize)
}

const debounceResize = () => {
  clearTimeout(debounceTimeout)
  debounceTimeout = setTimeout(() => {
    updateTextSize()
  }, 200)
}

onMounted(() => {
  window.addEventListener('resize', debounceResize)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', debounceResize)
})
</script>

<style>
:root {
  --text-size: 16px;
}

p {
  font-size: var(--text-size);
}
</style>

Here, the debounceResize method ensures that CSS variable updates only happen after the window resizing has stopped, improving performance.

Instead of building our own debounce function, we can use VueUse’s useDebounceFn helper function to achieve the same thing:

<script setup>
import { onMounted, onBeforeUnmount } from 'vue'
import { useDebounceFn } from '@vueuse/core'

const updateTextSize = () => {
  const newFontSize = window.innerWidth < 600 ? '14px' : '16px'
  document.documentElement.style.setProperty('--text-size', newFontSize)
}

const debounceResize = useDebounceFn(() => {
  updateTextSize()
}, 200)

onMounted(() => {
  window.addEventListener('resize', debounceResize)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', debounceResize)
})
</script>

3. Limit Scope

When possible, limit the scope of your CSS variable updates to specific components instead of applying them globally. This helps keep the impact localized and improves maintainability.

Here’s an example of scoped variable updates within a Vue component:

<template>
  <div ref="themeContainer" class="theme-container">
    <button @click="changeTheme">Switch Theme</button>
  </div>
</template>

<script setup>
import { useTemplateRef } from 'vue'

const container = useTemplateRef('themeContainer')

const changeTheme = () => {
  container.value.style.setProperty('--theme-color', '#007bff')
}
</script>

<style scoped>
.theme-container {
  --theme-color: #42b983;
  width: 100%;
  height: 100vh;
  background-color: var(--theme-color);
}

button {
  color: #fff;
  background-color: var(--theme-color);
}
</style>

In this example, the CSS variable --theme-color is scoped to the .theme-container, limiting its effect to just that component.

We can even rely on Vue.js’ SFC CSS features instead of defining CSS variables:

<template>
  <div class="theme-container">
    <button @click="changeTheme">Switch Theme</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const themeColor = ref('#42b983')

const changeTheme = () => {
  themeColor.value = '#007bff'
}
</script>

<style scoped>
.theme-container {
  width: 100%;
  height: 100vh;
  background-color: v-bind(themeColor);
}

button {
  color: #fff;
  background-color: v-bind(themeColor);
}
</style>

In this example, we didn’t have to define a CSS variable. Instead, we defined a themeColor variable ref and used v-bind() in CSS to bind its value to the corresponding CSS property.

Conclusion

Updating CSS variables with JavaScript, especially within a Vue.js application, offers a powerful way to create dynamic and responsive UIs. Whether you're implementing theme switching, adjusting styles for responsiveness, or reacting to user input, combining CSS variables with Vue’s reactivity opens up a world of possibilities.

Related Courses

Start learning Vue.js for free

Mostafa Said
Mostafa Said
With over 7 years of e-learning expertise honed at major companies like Vodafone Intelligent Solutions (_VOIS), Mostafa is full-time instructor at Vue School and a full-stack developer in the wild. He built and maintained dozens of apps using Vue, Nuxt, Laravel, Tailwind, and more. He merges modern teaching methods with coding education to empower aspiring developers of all experience levels. When he's not glued to the code editor, hackathons fuel his competitive spirit, with Deepgram and Appwrite grand prizes under his belt.

Comments

Latest Vue School Articles

Why Vue.js is a Great Starting Point for New Coders

Why Vue.js is a Great Starting Point for New Coders

Dive into Vue.js as a beginner with our comprehensive guide. Learn why Vue is perfect for starting your web development journey, with insights into its community, learning resources, and real-world uses.
Eleftheria Batsou
Eleftheria Batsou
The Vue Form Component Pattern: Robust Forms Without the Fuss

The Vue Form Component Pattern: Robust Forms Without the Fuss

Learn to create easy to use Vue forms without a library and take advantage of native HTML validation.
Daniel Kelly
Daniel Kelly

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.