This article provides a guide on implementing a dark theme and a light theme in a React application.
Implementation
React
First of all, let’s create a React context
. It will have a boolean flag to determine the selected theme and a function for toggling themes.
import React, { createContext, useState } from "react";
interface ContextProps {
darkTheme: boolean;
toggleTheme: () => void;
}
export const ThemeContext = createContext<ContextProps>({
darkTheme: true,
toggleTheme: () => {},
});
interface Props {
children?: React.ReactNode;
}
const ThemeProvider: React.FC<Props> = ({ children }) => {
const [darkTheme, setDarkTheme] = useState(true);
const toggleThemeHandler = () => {
setDarkTheme((prevState) => !prevState);
};
return (
<ThemeContext.Provider
value={{
darkTheme: darkTheme,
toggleTheme: toggleThemeHandler,
}}
>
{children}
</ThemeContext.Provider>
);
};
export default ThemeProvider;
Don’t forget to use the context.
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.scss";
import App from "./App";
import ThemeProvider from "./context/ThemeContext";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<ThemeProvider>
<App />
</ThemeProvider>
);
The main idea is to set the data-theme
attribute to dark
or light
depending on the chosen theme.
import React, { useContext, useEffect } from "react";
import { ThemeContext } from "./context/ThemeContext";
import styles from "./App.module.scss";
function App() {
const { darkTheme, toggleTheme } = useContext(ThemeContext);
useEffect(() => {
document.documentElement.setAttribute(
"data-theme",
darkTheme ? "dark" : "light"
);
}, [darkTheme]);
return (
<div className={styles.container}>
<div className={styles.card}>
<h2>Welcome to the app</h2>
<p className={styles.text__primary}>Primary texts</p>
<p className={styles.text__secondary}>Secondary texts</p>
</div>
<button className={styles.button} onClick={toggleTheme}>
Toggle Theme
</button>
</div>
);
}
export default App;
If everything is set up correctly, clicking Toggle Theme
button will toggle the data-theme
attribute.
SCSS
In this part, we will create two SCSS files.
Let’s create a new file _colors.scss
to define some colors that we intend to use. Typically, these color choices will come from the design system (ideally provided by product designers!).
/* Dark Theme Colors */
$neutral-0: #FFFFFF;
$neutral-500: #949497;
$neutral-700: #626264;
$neutral-900: #1A1A1C;
$dark-overlay-8: #2B2B2E;
$primary-500: #846bda;
/* Light Theme Colors */
$white-1: #FFFFFF;
$white-2: #F8F8F8;
$black-1: #232323;
$grey-2: #B4B4B7;
$grey-3: #949497;
$blue-1: #1274CF;
Next, let’s create _themes.scss
file where we will declare some CSS variables based on the data-theme
attribute. We will utilize the colors declared in _colors.scss
.
@use 'colors' as *;
html[data-theme="dark"] {
--background-color: #{$neutral-900};
--color: #{$neutral-0};
--primary-color: #{$primary-500};
--secondary-color: #{$neutral-500};
--card-background-color: #{$dark-overlay-8};
--card-border-color: #{$neutral-700};
}
html[data-theme="light"] {
--background-color: #{$white-2};
--color: #{$black-1};
--primary-color: #{$blue-1};
--secondary-color: #{$grey-3};
--card-background-color: #{$white-1};
--card-border-color: #{$grey-2};
}
Depending on the selected theme, it is necessary to update both the background-color
and default font color
. In index.scss
, import _themes.scss
and assign the values of these properties to the CSS variables previously defined in the _themes.scss
.
@use 'scss/themes' as *;
body {
background-color: var(--background-color);
color: var(--color);
overscroll-behavior: none;
margin: 0;
font-family: 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
From here, additional styling can be applied to each component. The core concept remains same: utilization of CSS variables declared in the _themes.scss
.
.container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 20px;
}
.card {
width: 40vw;
height: 40vh;
background-color: var(--card-background-color);
border: 1px solid var(--card-border-color);
border-radius: 4px;
padding: 16px;
display: flex;
flex-direction: column;
}
.text {
&__primary {
color: var(--primary-color);
}
&__secondary {
color: var(--secondary-color);
}
}
.button {
cursor: pointer;
width: 196px;
padding: 12px 16px;
font-size: 1.2rem;
border-radius: 4px;
border: 1px solid var(--primary-color);
outline: none;
color: var(--primary-color);
background-color: transparent;
}
Demo
Improvement
It would be better to store the selected theme in the local storage, allowing it to serve as the default theme the next time a user visits the application.