A good theme system should be scalable, performant, and easy to maintain across the app. The best approach depends on your stack, but CSS variables + context or a theme provider is a common and efficient strategy.
🧠 My Recommended Approach:
✅ Use CSS variables for global theming
✅ Manage theme state with React Context or a lightweight state store
✅ Persist user choice in localStorage
✅ Respect system preference (prefers-color-scheme) for better UX
🧾 Step-by-Step Implementation
1. Define CSS Variables for Themes
/* index.css */
:root {
--bg-color: #ffffff;
--text-color: #000000;
}
[data-theme="dark"] {
--bg-color: #121212;
--text-color: #ffffff;
}
2. Create a Theme Context
import React, { createContext, useEffect, useState } from "react";
export const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState(() => {
return localStorage.getItem("theme") ||
(window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
});
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prev => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
3. Use the Theme in Components
import React, { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
export default function App() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ background: "var(--bg-color)", color: "var(--text-color)", minHeight: "100vh" }}>
<h1>{theme === "dark" ? "🌙 Dark Mode" : "☀️ Light Mode"}</h1>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
🧭 Why This Works
- 🧪 CSS Variables → No need to rerender components; the browser handles color changes efficiently.
- 🧠 React Context → Single source of truth for theme state.
- 💾 localStorage → Persists theme preference across sessions.
- 🖥️ System Preference Support → Feels natural for the user.
- 🔄 Scalability → Easy to add more themes or dynamic styling.
🧰 Alternative Options
-
styled-components or Emotion:
UseThemeProviderwith a theme object:<ThemeProvider theme={theme === 'dark' ? darkTheme : lightTheme}> ... </ThemeProvider>Great for component-level theming and TypeScript support.
- Tailwind CSS:
Usedark:variant and control theclasson<html>or<body>.
🧪 Pro Tips
- Use a hook like
usePrefersColorSchemeto auto-detect system theme. - Consider context hydration for SSR/CSR consistency.
- Keep theme logic isolated so it can scale easily if more modes (e.g., “sepia” or “high contrast”) are added later.