DEV Community

Cover image for Using Dark Mode in your react app! πŸŒ™
Franklin Martinez
Franklin Martinez

Posted on

Using Dark Mode in your react app! πŸŒ™

Dark mode is one of the features that would look excellent implemented in your app, as it would improve the user experience inside your app.

So this time, I will show you how to implement dark mode with React and without any other external library!

🚨 Note: This post requires you to know the basics of React with TypeScript (basic hooks).

Any kind of feedback is welcome, thanks and I hope you enjoy the article.πŸ€—

Β 

Table of Contents.

πŸ“Œ Technologies to be used.

πŸ“Œ Creating the project.

πŸ“Œ First steps.

πŸ“Œ Creating Switch component.

πŸ“Œ Adding a few cards.

πŸ“Œ Styles for the themes.

πŸ“ Configuring variables for light theme.

πŸ“ Configuring variables for dark theme.

πŸ“ Using the variables in our style.

πŸ“Œ Adding the logic to switch between themes.

πŸ“ Controlling the state of the switch.

πŸ“Œ Refactoring the logic in a custom hook.

πŸ“Œ Conclusion.

πŸ“ Live Demo.

πŸ“ Source code.

Β 

πŸ’‘ Technologies to be used.

  • ▢️ React JS (version 18)
  • ▢️ Vite JS
  • ▢️ TypeScript
  • ▢️ CSS vanilla (You can find the styles in the repository at the end of this post)

Β 

πŸ’‘ Creating the project.

We will name the project: dark-light-app (optional, you can name it whatever you like).

npm init vite@latest
Enter fullscreen mode Exit fullscreen mode

We create the project with Vite JS and select React with TypeScript.

Then we run the following command to navigate to the directory just created.

cd dark-light-app
Enter fullscreen mode Exit fullscreen mode

Then we install the dependencies.

npm install
Enter fullscreen mode Exit fullscreen mode

Then we open the project in a code editor (in my case VS code).

code .
Enter fullscreen mode Exit fullscreen mode

Β 

πŸ’‘ First steps.

Now we first create a folder src/components and add the Title.tsx file it contains:

export const Title = () => {
    return (
        <h1>Dark - Light Mode </h1>
    )
}
Enter fullscreen mode Exit fullscreen mode

And now, inside the folder src/App.tsx we delete all the content of the file and we place the title that we have just created.

const App = () => {
  return (
    <div className="container">
      <Title />
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

It should look like this πŸ‘€:

Title

Β 

πŸ’‘ Creating Switch component.

Now inside the src/components folder we add the Switch.tsx file and place the following:

export const Switch = () => {
    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

It should look like this πŸ‘€:

Switch

Β 

πŸ’‘ Adding a few cards.

Again, inside the src/components folder, we add Card.tsx file.
we add the Card.tsx file.

First we will create the Layout component that will contain the cards.

export const LayoutCards = () => {
    return (
        <div className="grid-cards">
            <Card />
            <Card />
            <Card />
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Then, the Card component will look like this:

 export const Card = () => {
    return (
        <div className="card">
            <div className="card-image"></div>
            <h4 className="card-title">Lorem ipsum dolor sit.</h4>
            <p className="card-description">Lorem ipsum dolor sit amet consectetur adipisicing eli...</p>
            <div className="card-container-buttons">
                <button>Buy</button>
                <button>Show</button>
            </div>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

It should look something like this πŸ‘€:

Cards

Β 

πŸ’‘ Styles for the themes.

The idea is to use the variables with CSS for the dark and light theme.

Β 

🟑 Configuring variables for light theme.

We create a folder called src/styles and create the file var.css.
This file will be in charge of setting the CSS variables.

1- To set the variables inside CSS we use pseudo-class root as follows

:root {

}
Enter fullscreen mode Exit fullscreen mode

Inside we place the variables we are going to use. To define variables we use this syntax

--background: #f2f2f2;
Enter fullscreen mode Exit fullscreen mode

We have to place a double hyphen before the custom name of our property, then, we place a colon and add the value of that property.
Here are the other variables:

:root {
    --background: #f2f2f2;
    --text-primary: #0f0f0f;
    --text-secondary: #4e4e4e;
    --accent: #dfb017;
    --accent-hover: #cea315; 
    --border: #1f1e1e;
    --shadow: 7px 15px 13px -4px #00000056;
}
Enter fullscreen mode Exit fullscreen mode

These variables that we have just declared without for the light theme.

Β 

🟑 Configuring variables for dark theme.

Now let's define the variables for the dark theme.

To do this, the variable names have to be named exactly the same as the previous variables and we only change their value after the colon.

[data-theme='dark'] {
    --background: #05010a;
    --text-primary: #f2f2f2;
    --text-secondary: #a7a4a4;
    --accent: #6a5acd;
    --accent-hover: #5b4cbe; 
    --border: #696969;
    --shadow: 7px 15px 13px -4px #ffffff1b;
}
Enter fullscreen mode Exit fullscreen mode

Note that for the dark theme variables, we no longer use the pseudo-class root, but we reference a custom attribute that we are defining as theme.

This custom attribute, has to be placed in an HTML tag for the dark mode to work (Do not place the attribute manually, this will be done dynamically, using react).

But not in just any tag, it must be placed in the highest hierarchy tag, such as the body.

This is an example of how it should look

<body data-theme='dark' >
<!-- content -->
<body>
Enter fullscreen mode Exit fullscreen mode

If we place the data-theme attribute in the other tag with less hierarchy, only the content of that tag will use the dark mode.

For this reason, it should be placed in the tag with the highest hierarchy.

<body>
    <div data-theme='dark' >
        <!-- Dark theme -->
    </div>
    <div>
        <!-- Light theme -->
    </div>
<body>
Enter fullscreen mode Exit fullscreen mode

Β 

🟑 Using the variables in our style.

Now, notice that we have created a var.css file inside src/styles. But where do we import them?

Well, in my case I found it best to import them into the src/index.css file.

To import .css files into another .css file we use @import url() and add the path where the file to import is located.

This is a good practice to separate the CSS files as it helps to understand better the code of the styles.

By the way, you must place the import at the top of your file.

@import url('./styles/var.css');

body{
  font-family: 'Montserrat', sans-serif;
  font-weight: 600;
  transition: all .5s ease-in-out;
}
Enter fullscreen mode Exit fullscreen mode

Well, now, let's use the variables.

To use the variables, we make use of the function var() and inside we place the name of the variable exactly as we name it in our file var.css.

body{
  background-color: var(--background);
  color: var(--text-primary);
}
Enter fullscreen mode Exit fullscreen mode

Once the variables have been placed in the other styles (in the cards, switch and title), we will proceed with adding the logic for switching between themes.

Β 

πŸ’‘ Adding the logic to switch between themes.

First, we have to control the state of the switch to be able to get when it is 'on' / 'off' and depending on those values use one theme or another.

Β 

🟑 Controlling the state of the switch.

1- First we add a state. This state will be of type Theme, and will only accept the string 'dark' or 'light'.


type Theme = 'dark' | 'light'

export const Switch = () => {

    const [theme, setTheme] = useState<Theme>('light')

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

2- We create the function to control the switch event.

Which, receives as parameter the event that emits by default the input.
The function calls the setter setTheme and inside it makes an evaluation:

  • If the checked property of the input is set to true, it sets the 'dark' theme.

  • If the checked property of the input is false, it sets the 'light' theme.

Now, the function handleChange is going to be executed when the input of type checkbox has a change and for that reason we pass it to method onChange.

And the checked property of the same input, we will pass an evaluation, since the checked property only accepts boolean values. The evaluation will be:

  • If the value of the state theme is 'dark', the value of checked will be true.

  • If the value of the state theme is 'light', the value of checked will be false.

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

export const Switch = () => {

    const [theme, setTheme] = useState<Theme>('light')

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" onChange={handleChange} checked={theme === 'dark'} />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

3- And now, remember that we were going to place the custom attribute data-theme, well now it's time to do it.

For this we use an effect, which must be executed every time the value of the theme state changes. That's why we place it in its dependency array of the useEffect.

Then, inside the useEffect we execute the following:

document.body.setAttribute('data-theme', theme);
Enter fullscreen mode Exit fullscreen mode

Basically, we are accessing the body tag (because it is the highest point that encloses all our application), and we set a new attribute with the function setAttribute.

  • setAttribute, receives in this case two parameters:
    • the name of the new attribute.
    • the value for that new attribute.

So, we set the data-theme attribute with the value of the theme state.

The code should look like this:

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

export const Switch = () => {

    const [theme, setTheme] = useState<Theme>('light');

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light');

    useEffect(() => {

        document.body.setAttribute('data-theme', theme);

    }, [theme]);

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" onChange={handleChange} checked={theme === 'dark'} />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

And that's it, you would now have the functionality to switch between themes. πŸ₯³

switch theme

But now, we have a lot of logic in our file, so it's time to create a custom hook! πŸ‘€

Β 

πŸ’‘ Refactoring the logic in a custom hook.

We create a new folder inside src/hook create the useTheme.ts file and cut the logic from the Switch.tsx file and paste it into useTheme.ts.

We make the necessary imports.

import { useEffect, useState } from 'react';

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

export const useTheme = (): => {

    const [theme, setTheme] = useState<Theme>('light')

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    useEffect(() => {
        document.body.setAttribute('data-theme', theme);
    }, [theme])
}
Enter fullscreen mode Exit fullscreen mode

Then, this hook will return an array with two elements:

  • theme: the value of the theme state.
  • handleChange: the function, which receives an event, to change the state between themes and returns nothing.
import { useEffect, useState } from 'react';

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

type useThemeReturn = [ string, (e: ChangeEvent) => void ];

export const useTheme = (): useThemeReturn => {

    const [theme, setTheme] = useState<Theme>('light')

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    useEffect(() => {
        document.body.setAttribute('data-theme', theme);
    }, [theme])

    return [theme, handleChange]
}
Enter fullscreen mode Exit fullscreen mode

And also, we are going to receive as parameter the initial theme and add it to the initial value of the useState.

import { useEffect, useState } from 'react';

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

type useThemeReturn = [ string, (e: ChangeEvent) => void ];

export const useTheme = (initialTheme:Theme): useThemeReturn => {

    const [theme, setTheme] = useState<Theme>(initialTheme)

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    useEffect(() => {
        document.body.setAttribute('data-theme', theme);
    }, [theme])

    return [theme, handleChange]
}
Enter fullscreen mode Exit fullscreen mode

Now, it's time to call our custom hook.
Returned in the file src/components/Switch.tsx.

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

export const Switch = () => {

    const [theme, handleChange] = useTheme('dark');

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" onChange={handleChange} checked={theme === 'dark'} />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

And now it is definitely cleaner and easier to read our component! πŸ₯³

Β 

πŸ’‘ Conclusion.

The whole process I just showed, is one of the ways you can do the functionality to create dark mode and switch between themes, without using some external library. πŸŒ™

I hope I helped you understand how to make this functionality and that you manage to apply it in your future projects, thank you very much for getting this far! πŸ€—β€οΈ

I invite you to comment if you know any other different or better way of how to do this functionality. πŸ™Œ

Β 

🟑 Live Demo.

https://dark-light-theme-app.netlify.app

Β 

🟑 Source code.

GitHub logo Franklin361 / dark-light-app

Switch between dark - light themes without using external libraries. πŸŒ™

Dark Theme React JS 🌘

This time, we are going to implement the dark mode with React and without any other external library!.

Β 

Image or Gif

Β 

Features βš™οΈ

  1. Tema Light
  2. Dark Theme
  3. Switch between themes

Β 

Tecnologies πŸ§ͺ

  • React JS
  • TypeScript
  • Vite JS
  • Vanilla CSS 3

Β 

Installation 🧰

  1. Clone the repository (you need to have Git installed).
    git clone https://github.com/Franklin361/dark-light-app.git
Enter fullscreen mode Exit fullscreen mode
  1. Install dependencies of the project.
    npm install
Enter fullscreen mode Exit fullscreen mode
  1. Run the project.
    npm run dev
Enter fullscreen mode Exit fullscreen mode

Note: For running the tests, use the following command

    npm run test
Enter fullscreen mode Exit fullscreen mode

Β 

Links ⛓️

Demo of the application πŸ”₯

Here's the link to the tutorial in case you'd like to take a look at it! eyes πŸ‘€

  • πŸ‡²πŸ‡½ πŸ”—

  • πŸ‡ΊπŸ‡² πŸ”—

Top comments (7)

Collapse
 
robertbernstein profile image
Robert Bernstein

When I added the first two component files and updated the App.tsx file, I ran npm dev run and didn't see any of the changes. Did I miss a step? Do I need to do something to recompile the TypeScript?

Collapse
 
franklin030601 profile image
Franklin Martinez
  1. Have you already imported the first component files into the App.tsx file? πŸ€”

  2. The correct command to get the server up is "npm run dev".

If you like, you can check the grab in my GitHub repository πŸ™Œ.
github.com/Franklin361/dark-light-app

Collapse
 
gathsarah profile image
Gathsara

Grate Article..!

Collapse
 
khnbzkrt profile image
Hakan ANGIN

Great! Thanks for the article. I learned what how can i'll make dark-light theme and how can i'll use custom hook. Really helpful for me πŸ™Œ

Collapse
 
franklin030601 profile image
Franklin Martinez

I'm glad you find it useful, thanks for your comment! πŸ™Œ

Collapse
 
felipeferreiradev profile image
Felipe Ferreira

Excellent article, I will use it in my next project!!

Collapse
 
franklin030601 profile image
Franklin Martinez

I think it's a great idea! 😌