Add Dark Mode to React
Table of Contents
§ Create Dark/Light properties on Body Tag
There are Gatsby plugin options to incorporate DarkMode, but in this case we will use React's Context capability. This will let any React component read properties from React Context.
At the very root of your SCSS file, let's incorporate some CSS variables that we can use throughout the application. At the root level, we want to esablish the colors of these variables will change if the root class of your body
element has either the dark
class or not.
:root {
--primary-contrast-color: #FFFFFF;
--secondary-contrast-color: #000000;
[...]
--primary-background: #000000;
--secondary-background: #FFFFFF;
}
body.dark {
--primary-contrast-color: #000000;
--secondary-contrast-color: #FFFFFF;
[...]
--primary-background: #FFFFFF;
--secondary-background: #000000;
}
Now that we've done this, we can now use this variable to change colors of icons or other elements if this root class of dark
is present or not.
.genButton {
text-align: center;
transition: ease 0.3s;
border: 4px solid var(--secondary-contrast-color);
background-color: var(--secondary-background);
padding: 0.5em 1.1em;
top: 0;
box-shadow: none;
box-sizing: border-box;
text-decoration: none;
color: var(--primary-contrast-color);
}
§ Create Context Component
Before we create the Toggle Button, we will create a component that holds our variable. This variable will also use localStorage on the user's browser to hold the boolean that stores whether dark mode is on or off.
(You can read more about using React's Context API with Gatsby here )
import { createContext, useContext, useEffect, useState } from 'react';
interface AdvancedThemeContextProps {
isDarkMode: boolean;
toggleDarkMode: () => void;
}
export const AdvancedThemeContext = createContext<
AdvancedThemeContextProps | undefined
>(undefined);
const AdvancedThemeProvider = ({ children }) => {
// use react hooks for initial variable
const [isDarkMode, setIsDarkMode] = useState(false);
const toggleDarkMode = () => {
const newDarkModeState = !isDarkMode;
setIsDarkMode(newDarkModeState);
// use localStorage on user's browser
localStorage.setItem('isDarkMode', JSON.stringify(newDarkModeState));
};
useEffect(() => {
const storedDarkMode = localStorage.getItem('isDarkMode');
if (storedDarkMode) {
setIsDarkMode(JSON.parse(storedDarkMode));
}
}, []);
useEffect(() => {
document.body.classList.toggle('dark', isDarkMode);
}, [isDarkMode]);
return (
<AdvancedThemeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
{children}
</AdvancedThemeContext.Provider>
);
};
const useTheme = () => {
const context = useContext(AdvancedThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
export { AdvancedThemeProvider, useTheme };
This code does two things. It changes the root class of body
to dark
on and off, and also it allows you to use the isDarkMode
variable in any other part of your application you choose to wrap with AdvancedThemeProvider
. This can be useful if you are using Bootstrap Components that change their dark-mode properties on something other than a CSS class, such as data-bs-theme
.
import { Form } from 'react-bootstrap';
import { AdvancedThemeContext } from 'contexts/AdvancedThemeContext';
const FormThemeWrapper = ({ children, className, onSubmit }) => {
return (
<AdvancedThemeContext.Consumer>
{({ isDarkMode }) => { return (
<Form
className={className}
onSubmit={onSubmit}
data-bs-theme={isDarkMode ? 'dark' : 'light'} >
{children}
</Form>
);
}}
</AdvancedThemeContext.Consumer>
);
};
export default FormThemeWrapper;
§ DarkMode Toggle Button
I then create a DarkMode component I will later insert into my Top Banner.
import React from 'react';
import DarkModeIcon from 'assets/official-dark.svg';
import { useTheme } from 'contexts/AdvancedThemeContext';
const DarkStateToggle: React.FC = () => {
const { toggleDarkMode } = useTheme();
return (
<div
onClick={() => {
toggleDarkMode();
}}
style={{ padding: '10px 20px', cursor: 'pointer' }}
>
<DarkModeIcon
className="regularIcon"
role="button"
onClick={() => {
toggleDarkMode();
}}
/>
</div>
);
};
§ DarkMode Default
The last point, if you'd like to default the app for dark mode, the easiest way to add it is to create a gatsby-ssr.tsx
file and add it here.
exports.onRenderBody = ({ setBodyAttributes, setHtmlAttributes }) => {
setHtmlAttributes({ lang: 'en' });
setBodyAttributes({
className: 'dark', });
};
Good luck!