Master the Art of Personalization: Build a React & Tailwind Theme Switcher

Published: (December 12, 2025 at 07:50 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for Master the Art of Personalization: Build a React & Tailwind Theme Switcher

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-300 class 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 localStorage for 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.

Back to Blog

Related posts

Read more »