Implement Dark Mode with Zustand and Tailwind CSS in React
The Dark Side Of The Force Is A Pathway To Many Abilities Some Consider To Be Unnatural — Darth Plagueis
It’s time to embrace the dark side.
Today, I’ll show you how to create a dark mode react application with zustand (german) and Tailwind CSS. Let’s begin!
First off, what’s zustand?
Zustand is a small, fast and scaleable bearbones state-management solution. Has a comfy api based on hooks, isn’t boilerplatey or opinionated, but still just enough to be explicit and flux-like.
If you are familiar with redux
, this should be a piece of cake for you.
I am going to use create-react-app for this application. Fire up your terminal and run the following command
npx create-react-app react-dark-mode
Before we start with the cool stuff, we need to install our dependencies.
Run npm install zustand
to install zustand.
Setting up tailwind with create-react-app
is already documented in the official tailwind documentation. Navigate to this page to set up tailwind CSS with our project.
Once you are done, let’s begin!
Step 1: Setting up Tailwind Config 🔨
First, we need to set up some tailwind
config. Navigate to tailwind.config.js
inside your project root. If you followed the steps here correctly, there should already be the file there for you.
Add the following to your tailwind.config.js
:
Notice the darkMode
key, this is straight from the tailwind docs. Adding class
as the value allows us to manually control the dark mode. You can also use media
to toggle themes based on your OS theme.
Let’s move on.
Step 2: Building the UI 🖥️
Open App.js
and replace the content as below:
Let’s discuss what’s happening here. With Tailwind, all we need to do to implement dark mode styles is to add the dark
prefix. For example:
dark:bg-black dark:text-white
If you are familiar with tailwind, this concept won’t seem alien to you. The styles after the dark:
prefix will only be applied when your application is in dark mode. You can read about it more here.
Step 3: Constants ⚙
Before we move on to our toggle function, let’s declare our themes in a constants file. Create a new folder called constants
inside your src
directory. We’ll call it index.js
. Place the following code inside src/constants/index.js
This will help avoid typos throughout our codebase.
Now, we need a way to toggle our dark mode. With tailwind, we need to add class dark
to our root
element.
Step 4: Writing our Toggle Function 🌗
Now, let’s move to our toggle
function.
Let’s discuss what's happening here. Our applyThemePreference
function accepts the current theme as a parameter. Based on the current theme, we can add/remove the dark
attribute from our root
element.
That is if the current theme is light
, this will add a dark
class to our root
element and vice-versa. Now, we need a way to persist our theme so even if we reload the page, our theme of preference should persist. Enter zustand.
Zustand comes with a built-in middleware that allows us to persist our state to localStorage
. Let’s build our theme store.
Step 5: Manage Global State with Zustand 💪
Navigate to src
and create a new folder called stores
. Inside stores
, we will create a new file called useThemeStore
. Replace the contents src/stores/useThemeStore
with the following:
Let’s discuss what’s happening here. We have our themeStore
that exposes the current theme of our application and a function to toggle the theme. This will change our theme
variable from dark to light and vice-versa depending on the current theme of our application.
The persist
middleware
will allow our state to be saved in localStorage
with the key theme
. We can pass a configuration object to our persist middleware to change the name of the localStorage
key and other attributes.
Step 6: Say Hello to the dark side! 🌚
Head back to App.js
and modify it as below:
Let’s discuss what’s happening here.
Zustand exposes a hook to consume that state into our component. If you remember, we stored the current theme
and toggleTheme
inside our state. We can now consume the state using our useThemeStore
hook.
We pass a selector our hook that extracts those values for us. You can declare your selectors outside the component to prevent unnecessary calculations at each render. But that’s a story for another article.
Here, we get our theme
and toggleTheme
inside our component which we can use to manage the theme state of our application. We pass the toggleTheme
to our button onClick.
This will take care of changing the theme for the global state.
Now, to change the theme and reflect on your UI, we need to use our applyThemePreference
function coupled with our useEffect
.
Why do we need a
useEffect
here, you ask? Well, we need to add/remove thedark
class from ourroot
element every time the theme changes.
Click on the Toggle Theme button to see the magic! 🧙
And…. we are done!!! 🥳🎉💃🏻
But wait, we can do a little more. We can extract this functionality into a custom-hook.
Create a new folder hooks
inside src
and name it useTheme.js
. Inside your src/hooks/useTheme.js
, add the following code:
Inside your App.js
, you have a reusable hook!
There you go, we are done!!!!
Neat!
You can view more of my work at Github and follow me on Twitter if you are interested.
Thanks for reading!!