0
0
NextjsDebug / FixBeginner · 4 min read

How to Handle Dark Mode in Next.js: Simple and Effective Approach

To handle dark mode in Next.js, use React state or context to toggle a CSS class on the html or body element and apply styles accordingly. Use useEffect to sync the theme with user preferences or saved settings for a smooth experience.
🔍

Why This Happens

Many beginners try to toggle dark mode by directly changing styles without syncing with React state or without applying the theme class to the right HTML element. This causes the UI not to update or flicker on page load.

javascript
import { useState } from 'react';

export default function Home() {
  const [darkMode, setDarkMode] = useState(false);

  return (
    <div>
      <button onClick={() => setDarkMode(!darkMode)}>
        Toggle Dark Mode
      </button>
      <div style={{ backgroundColor: darkMode ? 'black' : 'white', color: darkMode ? 'white' : 'black' }}>
        Hello World
      </div>
    </div>
  );
}
Output
The background and text color change only inside the component, but the rest of the page remains unchanged and no smooth transition or system preference detection happens.
🔧

The Fix

Use React useEffect to add a dark class to the html element. This lets CSS handle the theme globally. Also, check the user's system preference on load and save the choice in localStorage to remember it.

javascript
import { useState, useEffect } from 'react';

export default function Home() {
  const [darkMode, setDarkMode] = useState(false);

  useEffect(() => {
    // Check saved theme or system preference
    const saved = localStorage.getItem('theme');
    if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
      setDarkMode(true);
      document.documentElement.classList.add('dark');
    } else {
      document.documentElement.classList.remove('dark');
    }
  }, []);

  useEffect(() => {
    if (darkMode) {
      document.documentElement.classList.add('dark');
      localStorage.setItem('theme', 'dark');
    } else {
      document.documentElement.classList.remove('dark');
      localStorage.setItem('theme', 'light');
    }
  }, [darkMode]);

  return (
    <div>
      <button onClick={() => setDarkMode(!darkMode)}>
        Toggle Dark Mode
      </button>
      <div className="content">
        Hello World
      </div>
      <style jsx>{`
        .content {
          background-color: white;
          color: black;
          padding: 20px;
          transition: background-color 0.3s, color 0.3s;
        }
        .dark .content {
          background-color: #121212;
          color: #e0e0e0;
        }
      `}</style>
    </div>
  );
}
Output
The page background and text colors switch smoothly between light and dark modes, respecting user preference and saving the choice for future visits.
🛡️

Prevention

Always manage dark mode state in React and apply a global CSS class to the html or body element. Use localStorage to remember user choice and prefers-color-scheme media query to respect system settings. Avoid inline styles for themes to keep CSS maintainable and transitions smooth.

⚠️

Related Errors

Flickering on page load: Happens if theme class is added after React hydration. Fix by adding initial theme class in _document.js or inline script.

Theme not persisting: Forgetting to save or read from localStorage causes theme to reset on refresh.

Key Takeaways

Use React state and effects to toggle a global CSS class for dark mode.
Check and respect user system preferences with the prefers-color-scheme media query.
Save user theme choice in localStorage to persist across visits.
Apply dark mode styles globally via CSS targeting the html or body element.
Avoid inline styles for theming to enable smooth transitions and maintainability.