How to implement a dark to light mode feature in your React/Sass project
Tags: react, webdev
Published: 2022-05-16 23:30:27

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](https://mftuxo.csb.app/) based on the popular TV show [Rick and Morty](https://en.wikipedia.org/wiki/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](#prerequisites)
- [Installation steps for the Demo App](#installation-steps-for-the-demo-app)
- [How does the light/dark theme toggle work?](#how-does-the-lightdark-theme-toggle-work)
- [How to install the useDarkMode hook](#how-to-install-the-usedarkmode-hook)
- [Creating a custom useTheme hook](#creating-a-custom-usetheme-hook)
- [Creating the light/dark theme toggle button](#creating-the-lightdark-theme-toggle-button)
- [Adding the useTheme hook to all the pages](#adding-the-usetheme-hook-to-all-the-pages)
- [How to add a Sass map for the light/dark theme styles](#how-to-add-a-sass-map-for-the-lightdark-theme-styles)
- [Applying the themes to the individual stylesheets](#applying-the-themes-to-the-individual-stylesheets)

## 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](https://classic.yarnpkg.com/en/).

## Installation steps for the Demo App

1. Clone the project

```sh
git clone https://github.com/jdwilkin4/Light-Dark-Theme-Starter-Code.git
```

2. `cd` into the `Light-Dark-Theme-Starter-Code` directory

```sh
cd Light-Dark-Theme-Starter-Code
```

3. Install the dependencies

```sh
yarn install
```

4. Start the local server

```sh
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](https://i.imgur.com/V0GJNrq.png)

## 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](https://github.com/donavon/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.

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

Underneath those imports, add these two variables:

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

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

```js
export const useTheme = () => {};
```

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

```js
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.

```js
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.

```js
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.

```js
return theme;
```

This is what the entire `useTheme` hook should look like.

```js
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`.

```js
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.

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

Now, we are going to create our Button component.

```js
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.

```js
const darkMode = useDarkMode(true);
```

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

```js
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.

```js
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.

```js
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](https://i.imgur.com/bYyoTlj.png)

Let's add some styling to our button.

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

```css
@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.

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

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

```js
const theme = useTheme();
```

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

```js
<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`.

```js
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.

```js
// 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.

```css
$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.

```css
@import "./colors";
```

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

```css
$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.

```css
$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-mode`classes.

```css
@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.

```css
@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:

```css
  (
    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.

```css
@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.

```css
@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:

```css
var(--color)
```

This is what the complete mixin should look like.

```css
@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.

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

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

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

```css
@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.

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

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

```css
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.

```css
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.

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

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

```css
  .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](https://i.imgur.com/9tQi78e.png)

## 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](https://irsymz.csb.app/) and [source code](https://codesandbox.io/s/light-dark-theme-final-code-irsymz).

---

_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](https://www.thisdot.co/)_