DEV Community

Samantha Laine
Samantha Laine

Posted on

Implementing Dark Mode in a React App

Author: Samantha Laine King

Last Updated: October 2025


🪄 Overview

This guide walks through adding a clean, accessible Dark Mode toggle to a React application using Tailwind CSS and localStorage.

It covers setup, theme persistence, and best practices for accessibility.


⚙️ Step 1: Set Up Tailwind for Theming

Tailwind provides built-in dark mode support.

In your tailwind.config.js file, enable class mode so users can manually toggle it:

// tailwind.config.js
export default {
  darkMode: 'class',
  theme: {
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

This allows you to switch themes by adding or removing the dark class on the root element.

🧩 Step 2: Create a Custom Hook for Theme Management

We’ll use a custom React hook to manage theme state and persist it in localStorage.

// useDarkMode.ts
import { useEffect, useState } from 'react'

export default function useDarkMode() {
  const [theme, setTheme] = useState(localStorage.theme || 'light')

  useEffect(() => {
    const root = window.document.documentElement
    const isDark = theme === 'dark'

    root.classList.toggle('dark', isDark)
    localStorage.setItem('theme', theme)
  }, [theme])

  return { theme, setTheme }
}
Enter fullscreen mode Exit fullscreen mode

💡 Step 3: Add the Toggle Button

Here’s how to create a reusable toggle button that switches between light and dark modes.

// ThemeToggle.tsx
import { Moon, Sun } from 'lucide-react'
import useDarkMode from './useDarkMode'

export default function ThemeToggle() {
  const { theme, setTheme } = useDarkMode()

  return (
    <button
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
      className="p-2 rounded-full border text-gray-700 dark:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700 transition"
      aria-label="Toggle dark mode"
    >
      {theme === 'dark' ? <Sun size={18} /> : <Moon size={18} />}
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

🎨 Step 4: Test Your Dark Mode

Wrap your app in a container that uses Tailwind’s dark variant:

// App.tsx
import ThemeToggle from './ThemeToggle'

export default function App() {
  return (
    <div className="min-h-screen bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 transition-colors">
      <header className="flex justify-between p-4">
        <h1 className="text-xl font-semibold">Dark Mode Example</h1>
        <ThemeToggle />
      </header>
      <main className="p-4">
        <p>Try toggling dark mode and refresh the page to see it persist!</p>
      </main>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

✅ Step 5: Best Practices & Accessibility Tips

Step 5: Best Practices & Accessibility Tips

  • Provide sufficient color contrast (use tools like contrast-ratio.com).

  • Add an aria-label to the toggle button for screen readers.

  • Ensure motion preferences are respected when using transitions.

  • Store the user’s preference in localStorage for a consistent experience.

✨ Summary

This implementation gives users control over their theme while maintaining accessibility and performance.
The setup is lightweight, scalable, and easy to extend for future design systems.

Thanks for reading!

Written with care by Samantha Laine King — Frontend Developer passionate about clean UI, accessibility, and developer education.

Top comments (0)