DEV Community

Cover image for How to implement a dark to light mode feature in your React/Sass project
Jessica Wilkins for This Dot

Posted on • Originally published at thisdot.co

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
Enter fullscreen mode Exit fullscreen mode
  1. cd into the Light-Dark-Theme-Starter-Code directory
cd Light-Dark-Theme-Starter-Code
Enter fullscreen mode Exit fullscreen mode
  1. Install the dependencies
yarn install
Enter fullscreen mode Exit fullscreen mode
  1. Start the local server
yarn start
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

Underneath those imports, add these two variables:

const lightTheme = "light-mode";
const darkTheme = "dark-mode";
Enter fullscreen mode Exit fullscreen mode

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

export const useTheme = () => {};
Enter fullscreen mode Exit fullscreen mode

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

const darkMode = useDarkMode();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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

import "../components/ThemeBtn.scss";
Enter fullscreen mode Exit fullscreen mode

Now, we are going to create our Button component.

const ThemeBtn = () => {};
export default ThemeBtn;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

const theme = useTheme();
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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

const theme = useTheme();
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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

$themes: ();
Enter fullscreen mode Exit fullscreen mode

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
  )
);
Enter fullscreen mode Exit fullscreen mode

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) {
}
Enter fullscreen mode Exit fullscreen 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 {
}
Enter fullscreen mode Exit fullscreen mode

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,
  )
Enter fullscreen mode Exit fullscreen mode

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 {
}
Enter fullscreen mode Exit fullscreen mode

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};
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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};
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

Applying the themes to the individual stylesheets

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

@import "./styles/light-dark-theme";
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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

@import "../styles/light-dark-theme";
Enter fullscreen mode Exit fullscreen mode

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

  .character-container {
    color: var(--textThemeColor1);
    background-color: var(--bgThemeColor2);
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)