React Context for Theme State
Build a ThemeProvider that exposes current theme and a toggle function to the entire component tree.
React Context for Theme State is a free React Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the React Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Creating ThemeContext
Start by calling createContext() with a default value that matches your context shape: const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} }). The default value is used only when a component reads the context without a parent Provider, which helps with testing isolated components.
The ThemeProvider Component
The ThemeProvider component is a wrapper that holds all theme logic. It reads the system preference on mount, manages the theme state, provides a toggleTheme function, and wraps children in ThemeContext.Provider value={{ theme, toggleTheme }}. Place it near the top of your component tree.
Consuming Context with useContext
Any descendant component can access the theme by calling useContext(ThemeContext). This returns the current context value including theme and toggleTheme. The component automatically re-renders when the theme changes, keeping it always in sync without prop drilling.
Syncing data-theme to the DOM
CSS variables depend on a data-theme attribute on the root element. In ThemeProvider, add a useEffect that runs whenever theme changes: document.documentElement.setAttribute('data-theme', theme). This bridges the React state and the CSS layer seamlessly.
Storing User Override in localStorage
To persist the user choice across page reloads, write to localStorage whenever the theme changes: localStorage.setItem('theme', theme). You can encapsulate this logic in a useLocalStorage custom hook that returns a state-like pair and persists every update automatically.
Initializing Theme from localStorage
When initializing the theme state, read from localStorage first: localStorage.getItem('theme'). If no stored value exists, fall back to the system preference via window.matchMedia('(prefers-color-scheme: dark)').matches. This initialization runs only once on mount.
Theme Priority Order
The correct priority from highest to lowest is: stored user preference in localStorage, then system OS preference, then a hard-coded default (usually light). This order ensures the user always has final control while still respecting their OS configuration if they have not explicitly chosen in the app.
The useTheme Convenience Hook
Directly calling useContext(ThemeContext) in every component exposes implementation details. Instead, export a useTheme hook: function useTheme() { return useContext(ThemeContext); }. Components now import useTheme and call it without knowing about the underlying context.
Providing a Toggle Function
The toggleTheme function switches between light and dark: setTheme(prev => prev === 'light' ? 'dark' : 'light'). Expose it through context so any button anywhere in the tree can trigger it. The state lives in one place; the toggle logic is shared everywhere.
Memoizing Context Value
The context value object is recreated on every render of ThemeProvider. Wrap it in useMemo to prevent unnecessary re-renders of all context consumers: const value = useMemo(() => ({ theme, toggleTheme }), [theme]). This is a common performance optimization for context providers.
Testing ThemeProvider
In tests, wrap renders in a custom renderWithTheme utility that uses ThemeProvider as a wrapper. You can also create a light mock context for isolated testing: ThemeContext.Provider value={{ theme: 'dark', toggleTheme: jest.fn() }}. This lets you test components in a specific theme state.
Theme Priority Order
What is the correct priority order for initializing the app theme, from highest to lowest priority?
Lesson Recap: React Context for Theme State
Create a ThemeContext and a ThemeProvider that manages theme state, syncs to localStorage, and sets data-theme on the document. Use a useTheme convenience hook for clean consumption. Priority order is: stored preference, then system preference, then default.
Frequently asked questions
Is the “React Context for Theme State” lesson free?
Yes — the full text of “React Context for Theme State” is free to read here on the web, and the React Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the React Academy course, upgrade to CoddyKit PRO.
What will I learn in “React Context for Theme State”?
Build a ThemeProvider that exposes current theme and a toggle function to the entire component tree. You practise React Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start React Academy?
No prior experience is required. React Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “React Context for Theme State” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this React Academy lesson?
Yes. Every React Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Detecting System Color Scheme
- CSS Variables for Theme Switching
- React Context for Theme State
- Persisting Theme and Preventing Flash