Theming and Dark Mode
In this page:
Storing the Current Theme in Context
A ThemeContext, holding the current theme name (like light or dark) and a function to change it, lets any component in the app read and react to the current theme, without manually passing it down through props everywhere.
Note: Pair this with the useLocalStorage pattern from earlier so the chosen theme survives a page reload.
Warning: Reading the theme value doesn't automatically restyle anything — components still need to actually use it to change their appearance.
Example: Storing the Current Theme in Context
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ThemeContext = React.createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState("light");
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
}
function ThemedBox() {
const { theme } = React.useContext(ThemeContext);
return <div style={{ background: theme === "dark" ? "#222" : "#fff", color: theme === "dark" ? "#fff" : "#000", padding: "16px" }}>Current theme: {theme}</div>;
}
function App() {
return <ThemeProvider><ThemedBox /></ThemeProvider>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Toggling Between Themes
A toggle button reads the current theme from context and calls setTheme with the opposite value, letting the user switch themes on demand. Every component reading the theme via useContext automatically re-renders with the new value.
Note: Place the theme toggle button somewhere globally visible, like a header, so users can find it easily.
Warning: If many components each independently read the theme, a theme change causes all of them to re-render — acceptable for a rare action like theme toggling, but worth knowing.
Example: Toggling Between Themes
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ThemeContext = React.createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState("light");
const toggle = () => setTheme(t => t === "light" ? "dark" : "light");
return <ThemeContext.Provider value={{ theme, toggle }}>{children}</ThemeContext.Provider>;
}
function ThemeToggle() {
const { theme, toggle } = React.useContext(ThemeContext);
return <button onClick={toggle}>Switch to {theme === "light" ? "dark" : "light"} mode</button>;
}
function App() {
return <ThemeProvider><ThemeToggle /></ThemeProvider>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Using CSS Variables for Theme Values
Instead of computing colors with JavaScript ternaries in every component, CSS custom properties (variables) can be defined once and swapped at the document root, letting plain CSS rules automatically pick up the new values everywhere they're used.
Note: Setting variables on document.documentElement.style (or toggling a class there) lets a single change cascade to every component using var(--color-name).
Warning: CSS variables need to actually be referenced in your stylesheets (via var(--name)) to have any effect — defining them alone changes nothing visually.
Example: Using CSS Variables for Theme Values
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [dark, setDark] = React.useState(false);
const vars = dark ? { "--bg": "#222", "--fg": "#fff" } : { "--bg": "#fff", "--fg": "#000" };
return (
<div style={{ ...vars, background: "var(--bg)", color: "var(--fg)", padding: "16px" }}>
<button onClick={() => setDark(!dark)}>Toggle theme (CSS variables)</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Hardcoding colors directly in components instead of referencing theme variables, making a later theme switch require editing every component.
- Not persisting the user's theme choice (e.g. to localStorage), so it resets on every page reload.
- Forgetting to also update things outside of individual components, like the page's overall background color.
- Theming lets an app switch between visual variants (like light/dark mode) using shared variables rather than hardcoded values.
- A common approach combines Context (to hold the current theme) with CSS variables or a theme object.
- Persisting the chosen theme (e.g. localStorage) keeps it consistent across page reloads.
- CSS custom properties (variables) can be swapped at the document root to restyle the whole app at once.
CSS custom properties supported in all modern browsers; Context API stable since React 16.3.
Chapter Quiz — Complete all 8 topics to unlock
0/8 topics done
Complete these topics first: