Supporting dark mode with CSS variables
Incorporating a dark mode into a website is more than just a design trend; it's a crucial aspect of creating a compelling user interface. One of the best ways to support dark mode or custom themes is by utilizing CSS variables – we can define our colors in the root selector and dynamically adjust them based on the chosen theme or mode.
Detecting User Theme Preferences
Many operating systems offer both light and dark themes, and the prefers-color-scheme
CSS media feature lets you detect the current color theme.
The prefers-color-scheme
CSS media feature is used to detect if a user has requested light or dark color themes. A user indicates their preference through an operating system setting (e.g. light or dark mode) or a user agent setting. – MDN
Manually Toggling Selected Theme
To add a theme toggle, we need to know what the current theme applied to the website is. For this we'll create a custom CSS data attribute and call it data-theme
. Instead of using a data attribute, we could also simply use HTML classes; it's up to you to choose what you want to use to identify the current theme. Then we get the OS preferred theme using prefers-color-scheme
(if any) and assign it to theme.Once the data attribute is set, we can change the values of certain CSS variables for each value of theme
like below:
:root {
--bg-color: #000;
--txt-color: #fff;
}
/* When the theme is set to light */
html[theme="light"] {
--bg-color: #fff;
--txt-color: #000;
}
/* When the theme is set to dark */
html[theme="dark"] {
--bg-color: #000;
--txt-color: #fff;
}
body {
background-color: var(--bg-color);
color: var(--txt-color);
}
Here, we set default values for the --bg-color
and --txt-color
variables on :root
. Then, we change these values based on whether the theme is set to light
or dark
using the html[theme=""]
CSS selector. This way, we are able to set custom values for these variables based on the current theme.
For a more natural feel, we can add a transition for the properties that change when the theme is toggled:
body {
transition-duration: 0.3s;
transition-property: var(--bg-color), var(--txt-color);
}
When the theme is toggled, we can ensure it's continuity by storing the theme in the localStorage
. This way, when the website is revisited in the future, it will remember the theme that was previously selected and display it accordingly.
Now let's write a function to toggle the theme and change the data-theme
attribute with prefers-color-scheme
in a React application. When the website is loaded, we'll first check for prefers-color-scheme: dark
in the window media list and check if theme
is set in localStorage
:
const [theme, setTheme] = useState(
localStorage.getItem("theme") ||
(window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light")
);
Then we can include a function in the component to change the current theme:
const switchTheme = () => {
setTheme(theme == "light" ? "dark" : "light");
localStorage.setItem("theme", theme);
};
Now when the page loads we can apply the data-theme
attribute to the html
element with useEffect hook like following
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
Here's the full App
component:
import { useEffect, useState } from "react";
export function App() {
const [theme, setTheme] = useState(
localStorage.getItem("theme") ||
(window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light")
);
const switchTheme = () => {
setTheme(theme == "light" ? "dark" : "light");
localStorage.setItem("theme", theme);
};
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
return (
<>
<button onClick={() => switchTheme()}>Change Theme</button>
<p>My Content</p>
</>
);
}
With theme
and setTheme
you can create buttons with dynamic icons for toggling dark mode. If you want to access them from anywhere in the application, you could use React context or a state management library like Zustand or Redux.
Until next time!