React Theme Switcher with Context
Table of Contents + −
In the last lesson, React Sharing Global State, you built shared state that many components read and update. Now let’s use it to build the classic theme switcher: one light/dark toggle that has to apply across the whole app at once.
🤔 Why is theme a perfect context example?
A theme is the textbook case for context. Here’s why:
- The theme affects the whole app: header, content, buttons, footer, all of it.
- The toggle button is in one place, but the theme it controls is needed everywhere.
- Passing the theme as a prop to every single component would be endless drilling.
- So you want one shared theme value any component can read, and one toggle that changes it for all.
🧩 Step 1: create the theme context and provider
We start with a context and a provider component that holds the theme state and a toggle function.
import { createContext, useState } from "react";
export const ThemeContext = createContext(null);
export function ThemeProvider({ children }) { const [theme, setTheme] = useState("light");
// a simple function to flip between light and dark function toggleTheme() { setTheme((prev) => (prev === "light" ? "dark" : "light")); }
return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> );}Let’s walk through the provider:
themeholds the current theme, starting as"light".toggleThemeflips it: if it’s light, go dark; if dark, go light. It uses the functional updater for safety.- We share both
themeandtoggleThemein thevalue. {children}is whatever we wrap, so everything inside can read and toggle the theme.
🌳 Step 2: wrap the app
We wrap the whole app in ThemeProvider, so every component shares the theme.
import { ThemeProvider } from "./ThemeContext";
function App() { return ( <ThemeProvider> <Header /> <Content /> <ThemeToggleButton /> </ThemeProvider> );}So Header, Content, and the toggle button all sit inside ThemeProvider. They can all read the theme, and the button can change it for everyone.
🔘 Step 3: the toggle button
The button reads toggleTheme from context and calls it on click. It can also show the current theme.
import { useContext } from "react";import { ThemeContext } from "./ThemeContext";
function ThemeToggleButton() { const { theme, toggleTheme } = useContext(ThemeContext);
return ( <button onClick={toggleTheme}> Switch to {theme === "light" ? "dark" : "light"} mode </button> );}See how the button works:
- It reads
themeandtoggleThemefrom the context. - Clicking it calls
toggleTheme, which flips the shared theme. - The label shows what it will switch to, so it reads naturally.
🎨 Step 4: apply the theme to the UI
Other components read the theme and style themselves based on it. Here a component sets its background and text color from the theme.
import { useContext } from "react";import { ThemeContext } from "./ThemeContext";
function Content() { const { theme } = useContext(ThemeContext);
const styles = { background: theme === "dark" ? "#222" : "#fff", color: theme === "dark" ? "#fff" : "#000", padding: "20px", };
return <div style={styles}>The current theme is {theme}.</div>;}Here’s how the theme reaches the screen:
Contentreads the sharedthemewithuseContext.- It builds a
stylesobject with dark or light colors based on the theme. - When the button toggles the theme,
Contentre-renders with the new colors automatically.
Output
(light mode)The current theme is light. [Switch to dark mode]
(after clicking the button)The current theme is dark. [Switch to light mode](background and text colors flip across the app)🔄 Why the whole app updates at once
This is the payoff of using context. One click changes everything:
- The theme lives once in
ThemeProvider. - The button changes it; the provider re-renders with the new theme.
- Every component reading the theme,
Content,Header, the button, gets the new value. - So they all switch together, from a single toggle, with no props passed between them.
That’s the full power of context: shared data, one source of truth, and the whole app reacting to one change.
⚠️ Common Mistakes
A common mistake is keeping the theme in the button’s own local state, so only the button knows about it.
// ❌ theme stuck in the button's local state - the rest of the app can't see itfunction ThemeToggleButton() { const [theme, setTheme] = useState("light"); // only this button knows}
// ✅ read the shared theme from context so the whole app shares itfunction ThemeToggleButton() { const { theme, toggleTheme } = useContext(ThemeContext);}Keep these in mind:
- Don’t keep the theme in a single component’s local state. It must live in the provider to be shared.
- Don’t forget to share
toggleThemein the value, or components can read the theme but never change it. - Don’t pass the theme down as props through every component. Reading from context is the whole point.
✅ Best Practices
A few habits for a theme switcher:
- Keep the theme and its toggle in one provider component.
- Read the theme with
useContextin any component that needs to style itself. - Share a clear toggle function rather than exposing the raw setter, so the logic stays in one place.
- For a real app, you’d also save the theme (for example in localStorage) so it survives a refresh.
Real apps often use CSS classes
Here we styled with inline styles to keep it simple. In real apps, people often set a class like dark on the top element and let CSS handle the colors. The context part is the same; only how you apply the theme changes.
🧩 What You’ve Learned
- ✅ A theme switcher is the classic context example: one shared theme used across the whole app
- ✅ Hold the
themestate and atoggleThemefunction in a provider component - ✅ Wrap the app in the provider so every component shares the theme
- ✅ The toggle button reads and calls
toggleThemefrom context - ✅ Other components read the
themeand style themselves from it - ✅ One toggle updates every reader at once, because the theme has a single home
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why is a theme a good fit for the Context API?
Why: The theme is needed everywhere but toggled from one button. Context shares it across the whole app without drilling props.
- 2
Where should the theme state live?
Why: Keep the theme in the provider so it's a single source of truth. If it lived in the button's local state, no other component could see it.
- 3
How does a component apply the current theme?
Why: A component reads the shared theme with useContext, then picks colors or a class based on whether it's light or dark.
- 4
What makes the whole app switch theme from one button click?
Why: With one source of truth in the provider, toggling the theme re-renders all components reading the context, so they all switch together.
🚀 What’s Next?
Now you’ve built a complete theme switcher with context. To finish the module, let’s gather the habits that keep context clean and fast as your app grows, and learn when to reach for it and when not to.