React  

How to Add Dark Mode Toggle in a React Website

Introduction

Dark mode is a popular feature that lets users switch your website’s color scheme to a darker version. It’s easier on the eyes in low-light conditions, can save battery on OLED screens, and improves overall usability. In this guide, you will learn simple, practical ways to add a dark mode toggle to a React site — using plain CSS, CSS variables, and a few helpful libraries. All examples are easy to copy and adapt.

1. Design Approach: CSS Variables (Recommended)

The easiest and most flexible way to implement dark mode is using CSS custom properties (variables). You define color tokens once and switch them by changing a single class on the <html> or <body> element.

Example: CSS variables

:root {
  --bg: #ffffff;
  --text: #111827;
  --muted: #6b7280;
}

[data-theme='dark'] {
  --bg: #0b1220;
  --text: #e6eef8;
  --muted: #9aa4b2;
}

body {
  background: var(--bg);
  color: var(--text);
  transition: background 0.25s ease, color 0.25s ease;
}

.button {
  background: transparent;
  color: var(--text);
}

By toggling data-theme="dark" on the document element, all color variables change instantly.

2. Simple React Toggle (Local State)

Use React state for a basic dark-mode toggle that applies only during the current session.

Component example

import React, { useState } from 'react';

export default function DarkToggle() {
  const [dark, setDark] = useState(false);

  const toggle = () => {
    setDark(prev => !prev);
    if (!dark) document.documentElement.setAttribute('data-theme', 'dark');
    else document.documentElement.removeAttribute('data-theme');
  };

  return (
    <button onClick={toggle} aria-pressed={dark}>
      {dark ? 'Switch to Light' : 'Switch to Dark'}
    </button>
  );
}

This is great for prototypes or simple sites, but the preference is lost after refresh.

3. Persisting Preference with localStorage

To remember the user’s choice across visits, store the preference in localStorage and read it on app load.

Hook: useTheme

import { useEffect, useState } from 'react';

export function useTheme() {
  const [theme, setTheme] = useState(() => {
    const saved = localStorage.getItem('theme');
    if (saved) return saved;
    // Optional: respect system preference
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    return prefersDark ? 'dark' : 'light';
  });

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme === 'dark' ? 'dark' : '');
    localStorage.setItem('theme', theme);
  }, [theme]);

  return [theme, setTheme];
}

Usage

import React from 'react';
import { useTheme } from './useTheme';

function App() {
  const [theme, setTheme] = useTheme();
  return (
    <div>
      <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>Toggle</button>
      <p>Current theme: {theme}</p>
    </div>
  );
}

This remembers the choice even after closing the browser.

4. Respect System Preference (prefers-color-scheme)

Many users prefer the OS-level theme. You can initialize the theme based on prefers-color-scheme and listen for changes.

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

prefersDark.addEventListener('change', (e) => {
  const newPref = e.matches ? 'dark' : 'light';
  // apply only if user hasn't set manual preference
});

Use this to set a good default but override it when the user toggles manually.

5. Accessible Toggle Button

Make sure the toggle is keyboard-focusable and has aria-pressed or aria-label for screen readers.

<button
  role="switch"
  aria-checked={theme === 'dark'}
  onClick={toggle}
>
  {theme === 'dark' ? 'Dark' : 'Light'}
</button>

Also ensure contrast ratios meet accessibility standards (WCAG).

6. Using Tailwind CSS with Dark Mode

If you use Tailwind CSS, enabling dark mode is straightforward: set darkMode: 'class' in tailwind.config.js and toggle the dark class on <html>.

// tailwind.config.js
module.exports = {
  darkMode: 'class',
  // ... rest
}

Example markup:

<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
  Hello
</div>

Then toggle:

document.documentElement.classList.toggle('dark');

7. Server-Side Rendering (Next.js) Considerations

When using SSR (Next.js), theme must be applied correctly during server render to avoid a flash of wrong theme (FOIT). Use one of these strategies:

  1. Inline script in _document.js: Read localStorage on first paint and set data-theme or class before React mounts.

// _document.js (simplified)
<script dangerouslySetInnerHTML={{ __html: `
(function(){
  try{
    var theme = localStorage.getItem('theme');
    if(!theme){
      var m = window.matchMedia('(prefers-color-scheme: dark)');
      theme = m.matches ? 'dark' : 'light';
    }
    if(theme === 'dark') document.documentElement.setAttribute('data-theme','dark');
  }catch(e){}
})();
`}} />
  1. Cookie-based server rendering: Store preference in a cookie and read it on the server to render the correct theme.

8. Using Libraries (Headless UI, use-dark-mode)

There are community hooks and libraries that simplify theme management, like use-dark-mode, next-themes (for Next.js), and UI libraries that include toggles. Example with next-themes:

npm install next-themes
import { ThemeProvider, useTheme } from 'next-themes';

// wrap _app.js
<ThemeProvider attribute="data-theme">
  <Component {...pageProps} />
</ThemeProvider>

Then use useTheme() to toggle.

9. Animations and Smooth Transitions

Keep transitions subtle to avoid jarring changes. Use CSS transitions on color and background:

* { transition: background-color .2s ease, color .2s ease; }

Avoid animating heavy layout properties.

10. Testing and Accessibility Checks

  • Test with keyboard only navigation.

  • Check contrast with tools like Lighthouse or the WebAIM Contrast Checker.

  • Test on real devices and different browsers.

Summary

Adding a dark mode toggle in a React website improves user experience and accessibility. The recommended approach is to use CSS variables and persist the user’s choice with localStorage (or cookies for SSR). Respect system preferences, ensure accessibility with proper ARIA attributes, and handle SSR carefully to avoid flashes of wrong theme. Use next-themes or Tailwind’s dark class for faster integration in common stacks.