Hey there, fellow developers! 🖥️
Have you ever found yourself loving the sleekness of dark mode? It's that cool, suave look that we often enjoy on code editors, social media platforms, and pretty much any app that offers this option. Even the newest Meta app, Thread, has hopped on the dark mode bandwagon! 🌙
Some people think it’s hard to implement dark mode, but it’s actually easy! Let’s see how to do it in React!
Here are the ingredients:
- Your React App
- SASS (not mandatory but suggested as it makes your life a lot easier)
- React Context.
Giving Your App That Dark Magic ✨
Alright, before we get into complex coding acrobatics, let's start simple: let's design your dark theme.
No need to dive deep into JavaScript and class toggling yet. First things first, make sure you've got your dark theme down pat. Create a fresh CSS class, let's call it
Now, let's put that class to work. Make gradual tweaks – start with background color and text color changes. One step at a time, my friend! For example, go for a darker background and lighter text.
If you're a SASS lover, your code might look like this:
Or if you're dancing with CSS:
Remember, simple is chic! As seen above, I focused on adjusting background and text colors.
Quick tip:
Once your dark mode transformation is ready, give it a double-check by removing the class. Everything should snap back to normal. If anything sticks around in dark mode, brush up those selectors! Elements should only shape-shift when they're wrapped in the
We're going to create a cozy little folder called
Let's use this function to craft our theme context. It's the single source of truth for our dark mode, meaning all info about its state must come from here!
Let’s go back to the file where we applied the dark mode class.
Wrap that previously created
Hold your horses – don't worry about the
To work our magic, we'll create a component, let's call it "Switch." This component will effortlessly switch between day and night modes with a single click. Don't worry if that sounds too fancy – a basic button will do the trick. The polished look? Save that for later!
Inside this component, we need a way to turn
In the same file as our first context, it's time to introduce the second one:
Now, here's the secret sauce:
Make sure you type
We're almost there, I promise!
Now, let's whip up a reducer function. This function is our go-to magician – it'll actually toggle our value. We'll toss this function into the
The first part's the current value, while the second is the kind of action we'll serve up. If you've mingled with Redux before, this pattern should ring a bell.
We’re going to create just one type of action: 'toggle'. I've opted for a
Here's our reducer function:
Let’s go apply this reducer.
Back on the file where we applied our first context, let’s apply the second context and create our toggling logic.
Back in the place where we created our first context, let’s wrap the
Let's bring this to life with the
That starting value is what the first parameter of our
Now, behold the final masterpiece, complete with a touch of class conditional enchantment:
With that checked off, let's dive into Switch component magic.
Using the
Ready for the grand finale? When you click, dispatch the 'toggle' action:
And voilà! That's a wrap on our grand dark mode adventure! ✨
theme__dark
. Remember, the name isn't set in stone – what matters is using it right. Slap this class onto your app's tag or the highest-level parent element you can reach. In my case, I'm using NextJS, so I created a
_app.tsx
file and added the class there. If you're working with Vanilla React, it could be in index.tsx
(or main.tsx
if you're vibing with Vite). This is where you'd normally spot your StrictMode
and .createRoot()
function.
//_app.*sx, index.*sx or main.*sx
<div className="theme__dark" >
<Component {...pageProps} />
{/*If you are using Vanilla React, you will find your <App/> here. */}
</div>
.theme__dark {
background-image: url("../public/assets/bg--dark.png");
transition: 0.5s;
* {
color: white !important;
div[class*="tags"] span {
background-color: rgb(153, 0, 255) !important;
}
}
#showcase article {
background-color: rgb(212, 148, 255, 0.187);
}
#footer,
nav {
background-color: rgb(212, 148, 255, 0.187);
}
#shadow * {
color: $text !important;
}
}
.theme__dark {
background-image: url("../public/assets/bg--dark.png");
transition: 0.5s;
}
.theme__dark * {
color: white !important;
}
.theme__dark div[class*="tags"] span {
background-color: rgb(153, 0, 255) !important;
}
.theme__dark #showcase article {
background-color: rgb(212, 148, 255, 0.187);
}
.theme__dark #footer, .theme__dark nav {
background-color: rgb(212, 148, 255, 0.187);
}
.theme__dark #shadow * {
color: $text !important;
}
background-color
and color
are inherited properties!
theme__dark
class.
Flipping the Light Switch ⚡
💡Handy Resources:
context
to keep things tidy. In here, we'll import createContext
from React. This function takes an initial value as its parameter and returns a Context object that we'll introduce as the Provider later on.
//context/index.ts
import { createContext } from "react";
export const ThemeContext = createContext(false)
<ThemeContext.Provider value={true}>
<div className="theme__dark">
<Component {...pageProps} />
</div>
</ThemeContext.Provider>
value
just yet. Next up, let's tackle the art of toggling this value.
true
into false
. Here's where our second Context enters the scene.
ThemeDispatchContext
. This context brings along a dispatch
function that lets us update the dark mode value from true
to false
for all app components.
import { createContext } from "react";
export const ThemeContext = createContext(false)
//typescript:
export const ThemeDispatchContext = createContext<React.Dispatch<{ type: string }>>(() => null);
//javascript:
//export const ThemeDispatchContext = createContext(() => null);
createContext
correctly in TypeScript, or it might raise an eyebrow later.
useReducer
hook, so it needs to follow a specific syntax: (value, action: { type: string }) => void
.
switch
case, but an if
statement's just as cool.
export function themeReducer(theme: boolean, action: { type: string }) {
switch (action.type) {
case 'toggle': return !theme
default: {
throw Error('Unknown action');
}
}
}
<ThemeContext.Provider value={true}> //we are about to change this value!!
<ThemeDispatchContext.Provider value={????????}> //what goes in here?
<div className="theme__dark" >
<Component {...pageProps} />
</div>
</ThemeDispatchContext.Provider>
</ThemeContext.Provider>
useReducer
hook. It takes two things: the reducer function we whipped up earlier and the starting value for our dark mode.
themeReducer
function will be.
const [themeRed, dispatch] = useReducer(themeReducer, false)
import { useReducer } from "react";
import "../styles/app.scss"
import { ThemeContext, ThemeDispatchContext, themeReducer } from "@/context";
export default function App({ Component, pageProps }: any) {
const [themeRed, dispatch] = useReducer(themeReducer, false)
return (
<ThemeContext.Provider value={themeRed}> {/*we replaced true with the value from the reducer.*/}
<ThemeDispatchContext.Provider value={dispatch}> {/* we finally gave a value to this other context. */}
<div className={themeRed ? "theme__dark" : undefined} > {/* if themeRed === true, apply the class 'theme__dark', otherwise don't apply any classes. */}
<Component {...pageProps} />
</div>
</ThemeDispatchContext.Provider>
</ThemeContext.Provider>
}
)
}
useContext
hook, pass the ThemeDispatchContext
object as a parameter.
Don't forget to do the same for ThemeContext
.
//Switch.tsx
const dispatch = useContext(ThemeDispatchContext)
const theme = useContext(ThemeContext)
<div onClick={() => dispatch({ type: "toggle" })}></div>
//Switch.tsx
import { useContext } from "react"
import { ThemeContext, ThemeDispatchContext } from "@/context"
const Switch = () => {
const dispatch = useContext(ThemeDispatchContext)
const theme = useContext(ThemeContext)
return <div onClick={() => dispatch({ type: "toggle" })}></div>
}
export default Switch
import { createContext } from "react";
export const ThemeContext = createContext(false)
export const ThemeDispatchContext = createContext<React.Dispatch<{ type: string }>>(() => null);
export function themeReducer(theme: boolean, action: { type: string }) {
switch (action.type) {
case 'toggle': return !theme
default: {
throw Error('Unknown action');
}
}
}
// _app.tsx, main.tsx or index.tsx
import { useContext, useReducer } from "react";
import "../styles/app.scss"
import { ThemeContext, ThemeDispatchContext, themeReducer } from "@/context";
export default function App({ Component, pageProps }: any) {
const [themeRed, dispatch] = useReducer(themeReducer, false)
return
<ThemeContext.Provider value={themeRed}>
<ThemeDispatchContext.Provider value={dispatch}>
<div className={themeRed ? "theme__dark" : undefined} >
<Component {...pageProps} />
</div>
</ThemeDispatchContext.Provider>
</ThemeContext.Provider>
}