Dark Mode

Developing Websites with Multiple Themes using CSS Variables

multiple themes using css variables

If you are looking to develop a website that has multiple themes or dark mode feature, then this article will show you how you can do it using CSS variables.

Introduction to CSS Variables

CSS variables allow us to declare variables to store repeatedly used values like color codes and font family names. We can then reuse the variable at multiple places instead of repeating the same color codes and font family names throughout the stylesheet. Also, it becomes easy to replace the values in the future if required.

For people who are familiar with CSS Preprocessors like SCSS, SASS, LESS, and Stylus – CSS Variables is a native solution to declare and use variables in a plain .css file.

Declaring CSS Variables

A variable declaration is made by using a variable name prefixed with a double hyphen (--).

For example,

:root {
  --primary-color: #FFF;
}

All CSS variables must be defined within a scope. In the above example, the :root pseudo-class is the scope. Variables declared with a scope :root can be used anywhere in the stylesheet.

However, if you want a limit the scope of the variable to say only the header having a class name .header, then we can do it like so:

.header {
  --header-bg-color: #3F3;
}

Now, the variable --header-bg-color can only be used inside .header and all its child elements.

Using the Variables

Once a variable is declared, it can be used by passing the variable name to the var function.

For example, instead of doing this:

.primary-btn {
  background-color: #FFF;
}

now you can use the variable like this:

.primary-btn {
  background-color: var(--primary-color);
}

That is all there is in the basics of CSS variables. If you are interested to learn more about it, check out this MDN page.

Themes using CSS Variables

Now that we have covered the basics of CSS variables, let us move forward to create multiple themes using CSS variables.

The scope of the variable is the key to creating multiple switchable themes using CSS variables.

The general idea is to define the same set of variables with different values for different scopes. Then we will swap the scope using some JavaScript to switch from one theme to another.

Step 1: Build the Default Theme

Assuming you have already built your webpage without using CSS variables.

Go through your stylesheet line by line, extract the color codes used, store them in a variable with a .default scope. Finally, replace the color codes with the variables. Like so,

.default {
  --primary-color: #F00;
  --secondary-color: #0F0;
  --background-default: #EFEFEF;
  --background-paper: #FFF;
  --text-primary: #333;
  --text-secondary: #454545;
  --text-error: #B33;
}

Lastly, add the class .default to your <body> tag so that your application starts up with the default theme.

Step 2: Build Custom Themes

Now, create new scopes with the same set of variables from the default scope. But, this time with different values.

.dark-theme {
  --primary-color: #F00;
  --secondary-colorr: #0F0;
  --background-default: #333;
  --background-default: #111;
  --text-primary: #FFF;
  --text-secondary: #EEE;
  --text-error: #B33;
}

Just go ahead and create as many themes as you want.

Step 3: Switching Themes using JavaScript

Create a simple dropdown using the <select> tag to allow users to switch to their preferred theme. The <option> tags will have the theme’s scope name – default and dark as values.

<select id="theme-selector">
  <option value="default">Default</option>
  <option value="dark">Dark mode</option>
</select>

Add a change event listener to the <select> tag, that will be triggered whenever the user attempts to change the theme. Read the user’s choice from the event object e.currentTarget.value. Like so,

window.onload = function() {
  // Set to default theme on load
  let currentTheme = default";

  const themeSelector = document.getElementById("theme-selector");

  // Add change event listener
  themeSelector.addEventListener("change", function(e) {
    // Get the user's choice from the event object `e`.
    const newTheme = e.currentTarget.value;
  });
};

Now that we know both the current theme scope and new theme scope, we can remove the current theme scope and inject the new theme scope. Like so,

window.onload = function() {
  // Set to default theme on load
  let currentTheme = "default";

  const themeSelector = document.getElementById("theme-selector");

  // Add change event listener
  themeSelector.addEventListener("change", function(e) {
    // Get the user's choice from the event object `e`.
    const newTheme = e.currentTarget.value;

    // Set the theme
    setTheme(currentTheme, newTheme);
  });

  function setTheme(oldTheme, newTheme) {
    const body = document.getElementsByTagName("body")[0];

    // Remove old theme scope from body's class list
    body.classList.remove(oldTheme);

    // Add new theme scope to body's class list
    body.classList.add(newTheme);

    // Set it as current theme
    currentTheme = newTheme;
  }
};

At this point, you should be able to switch themes.

Step 4: Persisting User’s Theme Preference

There is one issue with the above code. When the user refreshes the page, it resets back to the default theme.

There are two ways to fix it – either store the user’s theme preferences in the browser using localStorage or backend database. For simplicity, we will demonstrate the former approach.

When the page loads, try to read from the local storage and set it as the current theme. If nothing is stored in local storage, set the current theme as default.

Whenever the user changes the theme, store the new theme scope in the local storage, so that we can fetch it when the page reloads.

window.onload = function() {
  // Try to read from local storage, otherwise set to default
  let currentTheme = localStorage.getItem("mytheme") || "default";

  const themeSelector = document.getElementById("theme-selector");

  // Set the theme that we read from local storage
  setTheme("default", currentTheme);
  themeSelector.value = currentTheme;

  // Add change event listener
  themeSelector.addEventListener("change", function(e) {
    // Get the user's choice from the event object `e`.
    const newTheme = e.currentTarget.value;

    // Set the theme
    setTheme(currentTheme, newTheme);
  });

  function setTheme(oldTheme, newTheme) {
    const body = document.getElementsByTagName("body")[0];

    // Remove old theme scope from body's class list
    body.classList.remove(oldTheme);

    // Add new theme scope to body's class list
    body.classList.add(newTheme);

    // Set it as current theme
    currentTheme = newTheme;

    // Store the new theme in local storage
    localStorage.setItem("mytheme", newTheme);
  }
};

Now when you refresh the user preference will persist. But, the ideal solution would be storing user preference in a database, because the local storage approach won’t work when the user visits your page using a different device.

You can find the full example code here on Github.

A fun fact as a closing note, this blog provides a dark theme using the same approach described in this article. Do let me know your feedback on it in the comment section.

Written By
Praveen
Programmer | Tech Blogger | Multitasker | Learner Forever | Someone who believes knowledge grows by sharing | Looking forward to using spare time productively
See responses (2)