Skip to content

How to implement a dark to light mode feature in your React/Sass project

There are many ways to implement a light/dark theme feature on your website. But how can you create a clean solution that will be easy to use and maintain overtime?

In this article, I will show you how to create a light/dark theme toggle functionality using React and Sass.

I have created a demo project based on the popular TV show Rick and Morty. There are a couple of pages dedicated to the main characters all designed in dark theme.

I will walk you through how to add light theme styles, and how to toggle between the two themes. You can then use this light theme solution in your own projects.

🔗Table of Contents

🔗Prerequisites

This article assumes you have a basic fundamental knowledge of React, Sass, and the command line.

This demo project is using Yarn, so it is recommended that you install Yarn.

🔗Installation steps for the Demo App

  1. Clone the project
git clone https://github.com/jdwilkin4/Light-Dark-Theme-Starter-Code.git
  1. cd into the Light-Dark-Theme-Starter-Code directory
cd Light-Dark-Theme-Starter-Code
  1. Install the dependencies
yarn install
  1. Start the local server
yarn start

You should see the homepage with two links that will lead you to the Rick and Morty pages.

Rick and Morty Demo Homepage

🔗How does the light/dark theme toggle work?

We will be creating a button where users can select if they prefer dark or light mode, and this button will toggle between the two styles. By default, the initial setting will be for dark mode.

When the user refreshes the page, their theme preference will be saved in local storage.

🔗How to install the useDarkMode hook

We will be using an npm package called use-dark-mode which is a custom hook used to implement the toggle functionality between light and dark mode.

Keep your server running, open up a new tab in the terminal, and run the command yarn add use-dark-mode.

🔗Creating a custom useTheme hook

The goal of this hook is to return a string value of either light-mode or dark-mode based on the current mode we are in. We will then use this string value as a class and apply it to the JSX elements.

Open up your code editor, locate the src folder and create a new folder called utils. Inside the utils folder, create a new file called useTheme.js.

At the top of your useTheme.js file, include the React and useDarkMode imports.

import React from "react";
import useDarkMode from "use-dark-mode";

Underneath those imports, add these two variables:

const lightTheme = "light-mode";
const darkTheme = "dark-mode";

Below the variable declarations, you will create the useTheme hook.

export const useTheme = () => {};

Inside the useTheme hook, we want to include the useDarkMode hook and assign it to a const variable called darkMode.

const darkMode = useDarkMode();

The return value for the useDarkMode() hook is an object, and one of the property names we are going to use is called value. The value property is a boolean which represents if dark mode is on or not.

Next, we want to add a new state variable and assign it the dark theme value.

const [theme, setTheme] = React.useState(darkTheme);

We are then going to add a useEffect hook and update the theme based on each time the mode changes.

React.useEffect(() => {
  setTheme(darkMode?.value ? darkTheme : lightTheme);
}, [darkMode.value]);

We are adding darkMode.value to the dependency array because we want it to only re-run the effect when the value changes on re-render.

Lastly, we want to return our theme.

return theme;

This is what the entire useTheme hook should look like.

export const useTheme = () => {
  const darkMode = useDarkMode();
  const [theme, setTheme] = React.useState(darkTheme);
  React.useEffect(() => {
    setTheme(darkMode?.value ? darkTheme : lightTheme);
  }, [darkMode.value]);

  return theme;
};

🔗Creating the light/dark theme toggle button

Locate the src/components folder, and create a file ThemeBtn.js and a ThemeBtn.scss file.

Inside that file, add the imports for React, useDarkMode and useTheme.

import React from "react";
import useDarkMode from "use-dark-mode";
import { useTheme } from "../utils/useTheme";

Right underneath those imports, include your stylesheet for this button component.

import "../components/ThemeBtn.scss";

Now, we are going to create our Button component.

const ThemeBtn = () => {};
export default ThemeBtn;

Inside the ThemeBtn component, we are going to use the useDarkMode hook and set the value to true because we want the default to be set to dark mode.

const darkMode = useDarkMode(true);

We are also going to create a variable called theme and assign to it the useTheme hook.

const theme = useTheme();

Inside the return, we are going to create a button. Since darkMode is an object that has a property called toggle, we can use that in the onClick function to toggle between light and dark themes.

For the button text, we will create a ternary operator which will show the text of "Light mode" or "Dark mode" depending on the state of the theme.

return (
  <button className="btn-theme" type="button" onClick={darkMode.toggle}>
    {theme === "dark-mode" ? "Light mode" : "Dark mode"}
  </button>
);

In order to see our toggle button in action, we need to add it to one of the pages. Most people choose to add the toggle button to the navigation bar. For our demo project, we will add it to the App.js file.

Import the ThemeBtn into the App component, and add the <ThemeBtn /> just before the routes.

import ThemeBtn from "./components/ThemeBtn";
function App() {
  return (
    <>
      <ThemeBtn />
      <Routes>
        <Route path="/" element={<Homepage />} />
        <Route path="/rick" element={<RickSanchezPage />} />
        <Route path="/morty" element={<MortySmithPage />} />
      </Routes>
    </>
  );
}

You should now see the button in the browser. Try clicking on it and see the text change between light and dark mode.

light dark mode button

Let's add some styling to our button.

Open up the ThemeBtn.scss file and add these styles for the btn-theme class.

@import "../styles/colors";

.btn-theme {
  background-color: $purple100;
  border: none;
  color: $grey100;
  display: block;
  font-size: 1.2rem;
  font-weight: 600;
  width: 150px;
  padding: 5px;
  text-align: center;
  margin: 0;
  cursor: pointer;

  &:hover {
    background-color: $purple200;
  }
}

🔗Adding the useTheme hook to all the pages

We need to import the useTheme hook to all of our pages because we want to apply the dark and light mode classes to the JSX elements.

Inside the App.js file, import the useTheme hook.

import { useTheme } from "./utils/useTheme";

Inside the App component, create a variable called theme, and assign the hook to it.

const theme = useTheme();

Replace the empty React fragments with div elements, and apply the theme variable to the className.

<div className={theme}>
  <ThemeBtn />
  <Routes>
    <Route path="/" element={<Homepage />} />
    <Route path="/rick" element={<RickSanchezPage />} />
    <Route path="/morty" element={<MortySmithPage />} />
  </Routes>
</div>

For the Button.js file, import the useTheme hook, and create the theme variable like before. Then, add that variable to the className.

import { useTheme } from "../utils/useTheme";

export const Button = ({ text, path }) => {
  const theme = useTheme();
  return (
    <Link to={path} className={`btn ${theme}`}>
      {text}
    </Link>
  );
};

For the CharacterTemplate.js file, import the useTheme hook and create the theme variable like before. Then add that variable to the className for the div elements.

// here is the full JSX markup
<div className={theme}>
  <h1>{title}</h1>
  <Button text="Return Home" path="/" />
  <div className="flex-container">
    {characterInfo.map((character, id) => (
      <div key={id} className="character-container">
        <h2>{character.name}</h2>
        <img src={character.image} alt="character avatar" />
      </div>
    ))}
  </div>
</div>

🔗How to add a Sass map for the light/dark theme styles

Inside the styles folder, open up the colors file and add the $grey200: #f5f1f1; variable.

This is what the complete colors file should look like.

$blue700: #1a1a40;
$blue600: #2c2c66;
$black: #000;
$grey100: #fdfcfc;
$grey200: #f5f1f1;
$purple100: #7a0bc0;
$purple200: #650c9d;

Inside the styles folder, create a new file called _light-dark-theme.scss.

At the top of your Sass file, import the colors file.

@import "./colors";

Then, we are going to create a new Sass map called themes.

$themes: ();

Inside the themes map, we are going to add individual maps for the background and text colors used for the light and dark themes.

$themes: (
  bgThemeColor1: (
    darkTheme: $blue700,
    lightTheme: $grey100
  ),
  bgThemeColor2: (
    darkTheme: $blue600,
    lightTheme: $grey200
  ),
  textThemeColor1: (
    darkTheme: $grey100,
    lightTheme: $black
  )
);

We are now going to create a mixin called styles with an argument called $mode. This mixin will be used later on in the dark-mode and light-modeclasses.

@mixin styles($mode) {
}

Inside the mixin, we are going to create an @each rule that will iterate through each key value pair in the themes map.

@each $key, $map in $themes {
}

The $key represents each of the background and text colors we created (Ex. bgThemeColor1). The $map represents each of the values. For example:

  (
    darkTheme: $blue700,
    lightTheme: $grey100,
  )

Inside that @each rule, we are going to create another rule that iterates over each key/value pair for the individual maps.

@each $prop, $color in $map {
}

Inside that @each rule, we will create a condition that checks which mode we are in and applies the appropriate style to that class.

@if $prop == $mode {
  --#{$key}: #{$color};
}

The reason why we are adding the -- in front of the key, is because we want to reference these color variables in the individual stylesheets using CSS variable syntax.

For example:

var(--color)

This is what the complete mixin should look like.

@mixin styles($mode) {
  @each $key, $map in $themes {
    @each $prop, $color in $map {
      @if $prop == $mode {
        --#{$key}: #{$color};
      }
    }
  }
}

Below the mixin, we are going to add the light and dark theme styles to the appropriate classes using the @include rule.

.dark-mode {
  @include styles("darkTheme");
}

.light-mode {
  @include styles("lightTheme");
}

This is what the entire light-dark-theme file should look like.

@import "src/styles/colors";

$themes: (
  bgThemeColor1: (
    darkTheme: $blue700,
    lightTheme: $grey100,
  ),
  bgThemeColor2: (
    darkTheme: $blue600,
    lightTheme: $grey200,
  ),
  textThemeColor1: (
    darkTheme: $grey100,
    lightTheme: $black,
  ),
);

@mixin styles($mode) {
  @each $key, $map in $themes {
    @each $prop, $color in $map {
      @if $prop == $mode {
        --#{$key}: #{$color};
      }
    }
  }
}

.dark-mode {
  @include styles("darkTheme");
}

.light-mode {
  @include styles("lightTheme");
}

🔗Applying the themes to the individual stylesheets

Inside the App.scss file, import the light-dark-theme file.

@import "./styles/light-dark-theme";

We are going to replace the background and text colors with the variables we created earlier.

body {
  background-color: var(--bgThemeColor1);
  color: var(--textThemeColor1);
  text-align: center;
}

If you test out the light/dark theme toggle button, you will notice that the background and text colors will change.

It would be nice if there were a gradual transition between the two colors. We can accomplish this by using the CSS transition property.

body {
  background-color: var(--bgThemeColor1);
  color: var(--textThemeColor1);
  text-align: center;
  transition: background-color 0.5s ease;
}

Inside the CharacterTemplate.scss file, import the light-dark-theme file.

@import "../styles/light-dark-theme";

Then replace the background and text colors with the CSS variables we created earlier.

  .character-container {
    color: var(--textThemeColor1);
    background-color: var(--bgThemeColor2);

Go to the browser and test out the light/dark theme button. You should be able to see both themes.

morty page

🔗Conclusion

We have successfully created a light/dark theme solution using React and Sass.

You can implement this solution into your own projects and it will be easy to scale and maintain over time.

Here is the final demo project and source code.


This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit thisdot.co

You might also like

Javascript

Getting Started with RxJS

Javascript

Testing Web Components with Cypress and TypeScript

Javascript

Web Components Integration using LitElement and TypeScript

Javascript

Navigation Lifecycle using Vaadin Router, LitElement and TypeScript