Master the Art of Personalization: Build a React & Tailwind Theme Switcher
Source: Dev.to

Why This Matters in Real Projects
Imagine you’re building the next big social media app. You’ve got sleek profiles, an intuitive feed, and a vibrant community. But then, users start asking: “Can we have a dark mode?”
A well‑architected theme switcher solves this. We’ll leverage React’s Context API to create a global theme state that any component can access, and Tailwind CSS’s dark: variant to effortlessly apply theme‑specific styles. We’ll also persist the user’s choice in localStorage, so their preference is remembered on subsequent visits.
Let’s Build This Thing
Step 1: Set Up Your React & Tailwind Project
npx create-react-app theme-switcher-app
cd theme-switcher-app
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configure tailwind.config.js to enable dark mode:
// tailwind.config.js
module.exports = {
darkMode: 'class', // We'll toggle a 'dark' class on the HTML element
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
Step 2: Create the Theme Context
// src/contexts/ThemeContext.tsx
import React, { createContext, useState, useEffect, useContext } from 'react';
type Theme = 'light' | 'dark';
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext(undefined);
export const ThemeProvider: React.FC = ({ children }) => {
const [theme, setTheme] = useState(() => {
// Check localStorage first, then system preference
const stored = localStorage.getItem('theme') as Theme;
if (stored) return stored;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
});
useEffect(() => {
const root = document.documentElement;
root.classList.remove('light', 'dark');
root.classList.add(theme);
localStorage.setItem('theme', theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
};
return (
{children}
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
Step 3: Build the Theme Switcher Component
// src/components/ThemeSwitcher.tsx
import { useTheme } from '../contexts/ThemeContext';
const ThemeSwitcher = () => {
const { theme, toggleTheme } = useTheme();
return (
{theme === 'light' ? '🌙' : '☀️'}
);
};
export default ThemeSwitcher;
Step 4: Put It All Together
// src/App.tsx
import { ThemeProvider } from './contexts/ThemeContext';
import ThemeSwitcher from './components/ThemeSwitcher';
function App() {
return (
<ThemeProvider>
<div className="min-h-screen flex flex-col items-center justify-center bg-white dark:bg-gray-900 transition-colors duration-300">
<header className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">
Welcome to My Themed App
</h1>
</header>
<main className="text-center">
<ThemeSwitcher />
<p className="mt-4 text-gray-700 dark:text-gray-300">
Toggle the theme and watch the magic happen.
</p>
</main>
</div>
</ThemeProvider>
);
}
export default App;
What Most Tutorials Miss
- Respect system preferences first – the context checks
window.matchMedia('(prefers-color-scheme: dark)')before defaulting, making the app feel native. - Add smooth transitions – the
transition-colors duration-300class prevents jarring switches.
Performance Considerations
When using server‑side rendering (e.g., Next.js), you may see a flash of unstyled content because localStorage isn’t available on the server. Mitigate this by adding a small script in the <head> that runs before React hydrates:
<script>
const theme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.classList.add(theme);
</script>
Key Takeaways
- Use React Context for global theme state — cleaner than prop drilling.
- Tailwind’s
dark:variant makes theming almost effortless. - Persist preferences in
localStoragefor returning users. - Respect system preferences as the initial default.
- Add smooth transitions for a polished feel.
Now go ahead and give your users the power to choose. They’ll thank you for it.