DEV Community

Naomi Pham
Naomi Pham

Posted on • Updated on

How to Create Dark/Light Mode with React (Tailwind Styling)

As developers, we should not limit ourselves to just one thing. Let’s make our app more dynamic with both dark & light versions.

There are many tutorials about toggling Dark/Light mode with ReactJS. Here’s my simple solution using React hook and CSS classes. I hope it will be useful for your project.


Install React & Tailwind

The first step is to install Tailwind for React app. If you haven’t already, follow steps below to set it up:


Install React

# Create a React site

npm create vite@latest my-blog --template-react
Enter fullscreen mode Exit fullscreen mode

I use Vite for React set-up, but you can use any other build tool like create-react-app.

Select framework React and variant JavaScript.

# Install React dependency

cd my-blog
npm install
Enter fullscreen mode Exit fullscreen mode

# Start dev server

npm run dev
Enter fullscreen mode Exit fullscreen mode

Install Tailwind

# Install Tailwind in your app

npm install -D tailwindcss postcss autoprefixer
Enter fullscreen mode Exit fullscreen mode

# Create tailwind.config.js and postcss.config.js files

npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

# Add paths to all template files in tailwind.config.js

module.exports = {
   content: [
     "./src/**/*.{js,jsx,ts,tsx}",
   ],
   theme: {
     extend: {},
   },
   plugins: [],
 }
Enter fullscreen mode Exit fullscreen mode

# Import Tailwind to CSS - Add this code to start of your ./src/index.css file

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

👉 Official tutorial by Tailwind here

Now run npm run dev to see your site in a web browser.


Set up basic layout

If you look into src folder of your app, you will see there are two jsx files already set up: main.jsx and App.jsx.

Basically, App.jsx contains your components for the app (Navbar, Header, MainBody, Footer, etc.) while main.jsx is responsible for displaying App.jsx on the server. Components of your app usually lie within the components folder.

Vite does not give us a default components folder. So let’s create one .src/components and add a Nav.jsx file:

import React from "react";

export default function Nav() {
    return (
        <nav>
            <h1>hello world</h1>
        </nav>
    )
}
Enter fullscreen mode Exit fullscreen mode

 
We will add more code to this later. For now, just put a temporary <h1> to see how it’s rendered in the app.

To display Nav content, import Nav.jsx to App.jsx:

import Nav from "./components/Nav"

export default function App() {
    return (
    <Nav />
)}
Enter fullscreen mode Exit fullscreen mode

 
Now we can work on the dark/light mode.


Set up Dark/Light mode for React

Here are five steps we'll go through to set up Dark/Light Theme for React app:


Step 1: Set darkMode State

const [darkMode, setDarkMode] = React.useState(true)

Enter fullscreen mode Exit fullscreen mode

darkMode will be set to intial true boolean value. This means that by default the site will be dark theme. If you want the default theme to be light, use false instead.

👉 If you like to refresh your knowledge on state & props, check out this article by Ahmed.


Step 2: Create toggleDarkMode function

function toggleDarkMode() {
        setDarkMode(prevDarkMode => !prevDarkMode)
    }
Enter fullscreen mode Exit fullscreen mode

When toggleDarkMode is triggered, darkMode state will alternate between true and false.


Step 3: Trigger toggleDarkMode function

To trigger toggleDarkMode, you need to use onClick handler. This handler will be placed where the user clicks to toggle dark/light mode.

For this app, I will place the handler in the toggle-icon inside Nav component.

Here's my set up for Nav.jsx with a simple h4 and toggle-icon image:

import React from "react";

export default function Nav(props) {
    return (
       <nav className="grid grid-cols-2 w-full place-content-between items-center">
            <h4>Resume</h4>
            <img src="./assets/toggle-icon-dark.svg" />
        </nav>
    )
}

Enter fullscreen mode Exit fullscreen mode

 
It looks something like this on the server:

Dark-navbar

 
The idea is that when darkMode is set to true, the image will be toggle-icon-dark.svg. When darkMode is false, the image will be toggle-icon-light.svg.

Image description

 
But before changing the images, let's go back to App.jsx file and set up a few things.

Inside Nav element in App.jsx, add the keys darkMode & toggleDarkMode:

<Nav 
    darkMode={darkMode} 
    toggleDarkMode={toggleDarkMode} 
/>
Enter fullscreen mode Exit fullscreen mode

 
The first key will watch whether darkMode is true or not, while the second key will control the toggle.

When the toggle-icon is clicked, darkMode will be set either true or false. True will render the page in dark theme and false will render the page in light theme.

In step 1, we've set the darkMode state in the parent file App.jsx. To pass it on to the child file Nav.js, we have to use props.

First, add onclick to the toggle icon image in Nav.js:

import React from "react";

export default function Nav(props) {
    return (
        <nav className="grid grid-cols-2 place-content-between items-center w-full">
            <h4>home</h4>
            <img onClick={props.toggleDarkMode} 
                 className="self-left justify-self-end" 
                 src="./assets/toggle-icon-light.svg" />
        </nav>
    )
}
Enter fullscreen mode Exit fullscreen mode

 

Next use the tenary operator to toggle the images based on the dark/light mode:

src={props.darkMode ? "./assets/toggle-icon-dark.svg" : "./assets/toggle-icon-dark.svg"} 
Enter fullscreen mode Exit fullscreen mode

 
When props.darkMode is true, the icon image will be toggle-icon-dark.svg . If darkMode is false, it will set to toggle-icon-light.svg. As you click on toggle-icon image, toggle-icon-dark.svg and toggle-icon-light.svg will alternate due to the state we have set early on.

💡 Ternary operator is great when you need to choose between two options in React. When you want to render something on the page, try && conditional rendering.


Step 4: Create CSS classes for dark & light modes

So we’ve got the toggle working (toggle between true/false and icon image light/dark onClick).

Though if you take a look at the server, everything will look the same except the toggle image. That's because we haven't actually change the background and text-color when the mode changes.

There are many ways to toggle dark/light background and text color in Tailwind. For this example, I will create dark & light classes in plain CSS.

Here’s my code in CSS:

:root {
  --clr-bg-dark: #1f2937; /* gray-800 */
  --clr-bg-light: #fafafa; /* zinc-50 */

  --clr-text-dark: #1f2937; /* gray-200 */
  --clr-text-light: #fafafa; /* zinc-50 */
}

.dark {
  background-color: var(--clr-bg-dark);
  color: var(--clr-text-dark);
}

.light {
  background-color: var(--clr-bg-light);
  color: var(--clr-text-light);
}
Enter fullscreen mode Exit fullscreen mode

You can also configure CSS class with Tailwind utilities like this:

.dark {
  @apply bg-gray-800 text-zinc-50
}
Enter fullscreen mode Exit fullscreen mode

However, I will leave both solution here in case you want to code in plain CSS.


Step 5: Change background-color & text-color according to dark & light modes

Now you need to add dark/light classes depending the mode your site is on.

Return to App.jsx and add these lines of code inside the return:

return (
    <div className={`h-full w-full mx-auto py-2 
                    ${darkMode ? "dark" : "light"}`}
    >
    <Nav 
        darkMode={darkMode} 
        toggleDarkMode={toggleDarkMode} 
    />
    </div>
)
Enter fullscreen mode Exit fullscreen mode

The outer wrap <div> will set the background and text-color for the entire page. We set height and width to full so that it will cover the whole body.

Again, you can use tenary operator to toggle between two options. If darkMode is true, dark class will be added. Otherwise, light class will be added. dark and light classes will alternate based on the state of darkMode.


A small update:

Turned out Tailwind has its way to control Dark/Light mode with dark variant. (A big thank to @mangor1no and @hectorob91 for pointing this out to me!).

All you need to do is to configure darkMode in tailwind.config.js:

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
  darkMode: "class"
}
Enter fullscreen mode Exit fullscreen mode

Then, add a dark class in CSS:

.dark {
   @apply bg-gray-800 text-zinc-50;
}
Enter fullscreen mode Exit fullscreen mode

This will allow you to manually control darkMode with class.

Now in the App.jsx file, instead of adding a light class when darkMode is false, you can leave it as null or an empty string:

return (
    <div className={`h-full w-full mx-auto py-2 
                    ${darkMode ? "dark" : ""}`}
    >
    <Nav 
        darkMode={darkMode} 
        toggleDarkMode={toggleDarkMode} 
    />
    </div>
)
Enter fullscreen mode Exit fullscreen mode

As you already configure bg-gray-800 and text-zinc-50 for darkMode, Tailwind will automatically switch to dark text and white background (bg-zinc-50 and bg-gray-800) when the theme is light! That's why the light class is no longer necessary. Pretty cool, huh?

Also, anytime you want to change the property of a specific element based on Dark/Light mode, you just have to add a dark prefix. Here's an example:

<div classname="parent dark">
  <button classname="child bg-white dark:bg-black"></button>
</div>

Enter fullscreen mode Exit fullscreen mode

For this to work, the class dark must be added to the parent of the child component you want to initiate the change. Thus, a good practice is to add dark class early on in the HTML tree (in <html>, <body>, or a container wrapping your entire content) so you can apply darkMode to the children components.

I will leave the link to Tailwind Dark Mode documentation here if you like to read more.


⚡ Example of Dark/Light Mode in a React site:

Page-light

Page-dark

| Link to example |


How do you set up Dark/Light mode in ReactJS? Let’s me know! I'm easily reachable on Twitter

Top comments (27)

Collapse
 
mangor1no profile image
Mangor1no • Edited

Hi Naomi, TailwindCSS has it own way to control the dark/light color palletes base on the state, you just need to add dark: prefix before your classes. You can set the property darkMode inside tailwind.config.js file to media to support dark mode base on user OS preference, or set it to class to manually control it. More information can be found here: tailwindcss.com/docs/dark-mode.

One more thing you need is to implement a small script to handle the white screen flashing problem when the browser starts loading your page. Add a small script inside the <head> tag to block page rendering and set the theme base on current mode saved inside your localStorage. It will improve the user experience a lots, at least from my perspective 👨‍💻

Collapse
 
naomipham_ profile image
Naomi Pham • Edited

Hi @mangor1no, thanks so much for your comment! I have been aware of this when reading Tailwind document and will def test this out in my other projects. Also, the tip about <head> tag is great! I'm sure to implement it when trying out this option.

Collapse
 
mangor1no profile image
Mangor1no

Glad I can help :D

Collapse
 
shreyas_patel_12543 profile image
Shreyas Patel

I love this! Creating dark mode has always been super annoying for me as a freelancer, and realizing you can do it with the "dark:" media query in Tailwind is a huge win. I saw someone using Vivid (vivid.lol) to quickly create a dark mode by styling right in the browser with Tailwind - was wondering if you had any thoughts!

Collapse
 
naomipham_ profile image
Naomi Pham • Edited

Hi Aryaman, completely agree with you there. Tailwind dark variant is awesome. And wow, Vivid is truly witchcraft! Styling Tailwind by adjusting browser!! I'm not sure if I can tell you much since i have not used Vivid before. 😅 Have you tried it yet? How did it go for you?

Collapse
 
shreyas_patel_12543 profile image
Shreyas Patel

I just tried it today on a Hacktoberfest project - it's so cool. Still a little buggy in places, but for Tailwind styling it makes it so much faster. Setup instructions were a bit hidden, but I was able to install it on my project with the docs: docs.vivid.lol. You should definitely try it out - might make for a good article!

Thread Thread
 
naomipham_ profile image
Naomi Pham • Edited

That's awesome! I will definitely give it a try in my future projects. Thanks again for recommending!

Collapse
 
hectorob91 profile image
hectorob91

It’s cool. Thank you for this.
Just a question Why do you not create the color variant as a part of the tailwind theme? Then you can use it I'm CSS with the @apply for your custom classes.
Something like this:

.dark{
@apply bg-gray-800 text-slate-200
}

I mentioned it because you can get the most from tailwind instead of only using it in the components.
😌

Collapse
 
naomipham_ profile image
Naomi Pham

Wow that's awesome to know. I just started using tailwind so i didn't know. I will apply it from now on. Thanks so much! 🤩

Collapse
 
asadanik profile image
Asad Anik

I just founding like this blog. For pre-note before working like this feature (Dark/Light) Mode ... Thank you 💖💖

Collapse
 
naomipham_ profile image
Naomi Pham

That's so great to hear. Thank you, Asad! I hope it will be helpful for your project.

Collapse
 
asadanik profile image
Asad Anik

yes definitely 🥰🥰.. And please keep it up

Collapse
 
carlosjuniordev profile image
Carlos Junior

Hi Naomi, will def save this because im doing my own project

Thank you so much.

Collapse
 
naomipham_ profile image
Naomi Pham

That's so great to hear. Thank you, Carlos! 🧡

Collapse
 
mteheran profile image
Miguel Teheran

Petty clear!

Collapse
 
naomipham_ profile image
Naomi Pham

Thank you! I'm glad you find it helpful.

Collapse
 
aradwan20 profile image
Ahmed Radwan

Nice and smooth work Naomi

Collapse
 
naomipham_ profile image
Naomi Pham

Thank you!!

Collapse
 
darabgif profile image
Darab

This is awesome

Thanks for this

Collapse
 
naomipham_ profile image
Naomi Pham

Thanks so much, Darab! Your comment made me day!

Collapse
 
jt3k profile image
Andrey Gurtovoy • Edited

I have no doubt that with the same ease you can learn to put on pants over your head. but why do you need it?
same with tailwind, it doesn't help app development, it's just annoying

Collapse
 
naomipham_ profile image
Naomi Pham • Edited

Hi Andrey, thanks for your comment! I guess I just like Dark mode as it looks cool and helps adjust my eyes in the evening. 😅 As for Tailwind I find it really useful without having to type a bunch of CSS from the scratch in index.css file. I don't have a lot of experience in app development yet so I don't know if it will work in large-scale projects. But for me right now when working on small projects like blog or personal portfolio, I think Tailwind is great.

Also, I love CSS utility classes. One class to perform only one task. Even before Tailwind I use utility classes a lot for styling with CSS. Tailwind does not replace CSS really but it makes it easier to implement ideas in my head.

Collapse
 
muhammadazfaraslam profile image
Muhammad Azfar Aslam

Good article for beginners to understand the concept of themes. Simple and to the point.

Collapse
 
naomipham_ profile image
Naomi Pham

Thanks so much! 🧡🧡

Collapse
 
jonassanthosh profile image
Jonas

Much helpful!

Collapse
 
naomipham_ profile image
Naomi Pham

Wow, thank you so much Jonas!

Collapse
 
jais99 profile image
Jaisal Srivastava

Flowbite has dark mode component that anyone can utilise that makes this easy like cake! flowbite-react.com/docs/customize/...