Create a Dark Mode Button in React with Tailwind
During the redesign of the blog, I was surprised at how easy it was to add a dark mode in Tailwind, so I thought why not add a small button that could change it at will (nothing fancy), and since I was going to do that, I also thought about recording the process 🙂.
In this tutorial, we'll learn how to create a simple dark mode button in our React application with Tailwind CSS.
Acceptance criteria
As always, before we start, let's define the acceptance criteria for this feature:
- Create an application with dark mode
- Add a button to manually change the application mode
- The mode should change automatically if the user has dark mode set on their operating system settings
I think that's enough, now let's get to work!
Prerequisites
I lied 🙈, before we start... This tutorial assumes that you have intermediate React knowledge (or basic if think so). Also, you'll need a React application.
If you don't have one yet, I suggest using Vite. You can find more information in the official documentation.
It's pretty simple and the assistant will help you through the process, now, let's get to work! (for real this time).
Step 1: Create the dark mode
First of all, we need our application to have a dark mode to switch to, for practical purposes of this tutorial, we'll create a very basic site using Tailwind. For example, this simple application:
1<main>2 <div className='flex flex-col items-center px-4'>3 <h1 className='mt-8 text-4xl font-bold'>Hello, world!</h1>4 <p className='mt-4 text-lg'>Wlcome to my website!</p>5 </div>6</main>7
Step 1.1: Create the "light mode"
The magic of Tailwind (or misfortune, depending on who you ask) is that you can
add all the styles you need through utility classes,1 so to shape our site,
we simply add the colors we need in the form of bg-
and text-
classes:
1<main>2 <div className='flex flex-col items-center bg-yellow-100 p-4 text-slate-900'>3 <h1 className='mt-8 text-4xl font-bold text-red-700'>Hello, world!</h1>4 <p className='mt-4 text-lg'>Welcome to my website!</p>5 </div>6</main>7
Would give us something like this:
Hello, world!
Welcome to my website!
You can find more information about Tailwind colors in the official documentation. Or you can extend the color palette with your own configuration.
Now this type of pattern is... controversial in the web development community, but for practical reasons and for the purposes of this tutorial, it serves us very well. The advantage of this approach is that we can change the colors of our application without having to modify the source code, simply by changing the CSS classes. This gives us greater development speed, but it can become complicated to maintain in larger applications.
Step 1.2: Create the "dark mode"
Once we have an idea of how we want our site to look during the day, we can start
playing with the dark mode, for this (and because we use Tailwind), we simply add
the color classes we need for the dark mode with the dark:
prefix.
1<main>2 <div className='flex flex-col items-center bg-yellow-100 p-4 text-slate-900 dark:bg-orange-950 dark:text-yellow-50'>3 <h1 className='mt-8 text-4xl font-bold text-red-700 dark:text-red-200'>4 Hello, world!5 </h1>6 <p className='mt-4 text-lg'>Welcome to my website!</p>7 </div>8</main>9
Easy, isn't it? Well, that's it! What Tailwind does behind the scenes is to look
for the user's preference, and if they have dark mode set on their operating system,
it will automatically apply the dark:
classes to the elements that have that class.
So, what we would normally do like this:
1@media (prefers-color-scheme: dark) {2 :root {3 --color--foreground: #431407; /* bg-orange-950 */4 --color--background: #fefce8; /* text-yellow-50 */5 }6}7
Now we simply do it like this:
1<div className='dark:bg-orange-950 dark:text-yellow-50'>Hello, world!</div>2
In addition to the dark
prefix, Tailwind also offers the hover:
, ::after
, lg:
,
among others.
And once the user's preferences change, Tailwind will take care of changing the colors automatically:
Hello, world!
Welcome to my website!
Step 2: Add a button that changes the mode
So far we have a website that changes from light to dark depending on the user's operating system preferences, but what if the user wants to change it manually? For that we need a button.
Step 2.1: Creating the button
Since we continue to use Tailwind, we can quickly add a button with the bg-
, text-
,
rounded
, p-
classes.
1<button2 aria-label='Switch to dark mode'3 className='w-min rounded-full bg-yellow-800 p-2 text-yellow-50 dark:bg-yellow-100 dark:text-slate-900'4>5 <SunIcon className='h-6 w-6' />6</button>7
And we'll end up with something like this:
Make sure the button has an aria-label
to make it accessible.
Remember what I said about utility classes? Well, here is where it starts
to get complicated, if you notice the className
attribute of the button is a bit
long, and if we need to change the background color, we would have to change it in
two places, which can be a bit tedious. To avoid this, you can create your own
reusable components.
And with the classes we just added, the button will change like this:
Step 2.2: Putting the styles together
Now that we have our component and our button, we need to put them together, for this, I won't explain all the classes, but basically we need an extra container and add the button to our component.
1<main>2 <div className='flex flex-col items-center bg-yellow-100 p-8 text-slate-900'>3 <div className='flex w-full justify-end'>4 <button5 aria-label='Switch to dark mode'6 className='w-min rounded-full bg-yellow-800 p-2 text-yellow-50 dark:bg-yellow-100 dark:text-slate-900'7 >8 <SunIcon className='h-6 w-6' />9 </button>10 </div>1112 <h1 className='text-4xl font-bold text-red-700'>Hello, world!</h1>13 <p className='mt-4 text-lg'>Welcome to my website!</p>14 </div>15</main>16
And with that, we'll have our result:
Hello, world!
Welcome to my website!
Step 3: Switching the mode
So far our component will change color automatically depending on the user's operating
system preferences, but now we need our button to manually trigger the change.
In React, we can use useState
to handle the state of our component.
1import { useState } from 'react';23export function Component() {4 const [isDarkModeActivated, setIsDarkModeActivated] = useState(false);56 const toggleDarkMode = () => {7 setIsDarkModeActivated(!isDarkModeActivated);8 };910 return (11 <main>12 <div className='flex flex-col items-center bg-yellow-100 p-4 text-slate-900 dark:bg-orange-950 dark:text-yellow-50'>13 <div className='flex w-full justify-end'>14 <button15 aria-label='Switch to dark mode'16 className='w-min rounded-full bg-yellow-800 p-2 text-yellow-50 dark:bg-yellow-100 dark:text-slate-900'17 onClick={toggleDarkMode}18 >19 <SunIcon className='h-6 w-6' />20 </button>21 </div>2223 <h1 className='mt-8 text-4xl font-bold text-red-700 dark:text-red-200'>24 Hello, world!25 </h1>26 <p className='mt-4 text-lg'>Welcome to my website!</p>27 </div>28 </main>29 );30}31
With that, by clicking our button, the state of isDarkModeActivated
will change
(it will be toggled between true
and false
).
Tailwind offers another way to activate dark mode, so that it does not only depend
on the operating system preferences, but also by adding a dark
class to the html
,
however this must be activated in the Tailwind configuration file: tailwind.config.ts
.
1import type { Config } from 'tailwindcss';23const config: Config = {4 darkMode: 'selector',5};67export default config;8
The official documentation indicates that the dark
class must be added to the <html>
for it to work correctly. However, I realized that it only needs to be in the parent
element of the component we want to change. Normally we want the whole site to change,
so we add it to the <html>
. Here we simply add it to the main container.
And if we now add the dark
class to the main container, our component will magically
change color:
Hello, world!
Welcome to my website!
Step 3.1: Plugging in the button
When we click the button, the state of our application changes, but how do we make
our component change color? For this, we simply need to add the dark
class to the
main container if isDarkModeActivated
is true
.
1import { useState } from 'react';23export function Component() {4 const [isDarkModeActivated, setIsDarkModeActivated] = useState(false);56 const toggleDarkMode = () => {7 setIsDarkModeActivated(!isDarkModeActivated);8 };910 return (11 <main className={isDarkModeActivated ? 'dark' : ''}>12 <div className='flex flex-col items-center bg-yellow-100 p-4 text-slate-900 dark:bg-orange-950 dark:text-yellow-50'>13 <div className='flex w-full justify-end'>14 <button15 aria-label='Switch to dark mode'16 className='w-min rounded-full bg-yellow-800 p-2 text-yellow-50 dark:bg-yellow-100 dark:text-slate-900'17 onClick={toggleDarkMode}18 >19 <SunIcon className='h-6 w-6' />20 </button>21 </div>2223 <h1 className='mt-8 text-4xl font-bold text-red-700 dark:text-red-200'>24 Hello, world!25 </h1>26 <p className='mt-4 text-lg'>Welcome to my website!</p>27 </div>28 </main>29 );30}31
And with that, by clicking the button, the color of our component will change,
Hello, world!
Welcome to my website!
Step 3.2: Maintaining operating system preferences
There is a small detail that comes with this implementation, and that is that when we
select the selector
dark mode as we did above, Tailwind will not take care of
changing the color of our component if the user changes the system preferences, this
fortunately is easy to solve, we simply add the media query within the Tailwind
configuration file. Like this:
1import type { Config } from "tailwindcss";23const config: Config = {4darkMode: [ 'variant', [5'&.is(.dark *)',6'media (prefers-color-scheme: dark) { & * }',7],8};910export default config;11
The way this works is thanks to the magic of &
, which refers to the current selector,
in this case html
, and *
which refers to all elements within html
. So, what we are
saying is:
- If the current selector (
html
) has the classdark
, then apply the styles to all children. - If the operating system has dark mode set, then apply the styles to all children of
html
.
And with that, Tailwind will take care of changing the color of our component if the user changes the preferences of their operating system.
Next steps
And with that, we have finished, to recap, let's see the requirements we had:
- Create an application with dark mode
- Add a button to manually change the application mode
- The mode should change automatically if the user has dark mode set on their
We have checked all the requirements, however in my opinion there is still a lot that can be done to improve our button, which you could try (or I'll leave it for another tutorial), for example:
- Add an animation to the button
- Change the button icon depending on the state of dark mode
- Add a tooltip to the button
- Save the user's manual preference in the
localStorage
And many other things, but for now, I think that's enough. If you have any questions or suggestions, don't hesitate to contact me through my social networks or the contact form.
Thanks for reading!