I built a custom Dark Mode for React + Tailwindcss

CODEWITHG.COMCODEWITHG.COM
3 min read

I recently wrote an article on creating dark mode in Next.js. Today, I'm sharing an even quicker method in React.

Step 1: Install TailwindCSS and Lucide-React

Install TailwindCSS and lucide-react:

npm install tailwindcss lucide-react
# or
yarn add tailwindcss lucide-react

Step 2: Configure TailwindCSS for Dark Mode

Enable the dark mode class strategy in tailwind.config.js:

module.exports = {
  darkMode: ['class'],
  // other configurations...
};

Step 3: Create a Dark Mode Toggle Component

Create a DarkMode component to toggle dark mode:

"use client"

import { Moon, Sun } from "lucide-react";
import { useState, useEffect } from 'react';

export const useDarkMode = () => {
  const [dark, setDark] = useState(false);

  useEffect(() => {
    const storedDarkMode = localStorage.getItem('dark-mode');
    if (storedDarkMode) {
      setDark(storedDarkMode === 'true');
    }
  }, []);

  useEffect(() => {
    localStorage.setItem('dark-mode', dark);
  }, [dark]);

  return [dark, setDark];
};


export default function DarkMode({ dark, setDark }) {
  useEffect(() => {
    if (typeof window !== "undefined") {
      const root = window.document.documentElement;
      if (dark) {
        root.classList.add('dark');
      } else {
        root.classList.remove('dark');
      }
    }
  }, [dark]);

  return (
    <div className="cursor-pointer" onClick={() => setDark(!dark)}>
      {dark ? <Sun className="h-[1.2rem] w-[1.2rem]" /> : <Moon className="h-[1.2rem] w-[1.2rem]" />}
    </div>
  );
}

Here's a brief explanation of the code:

  1. useDarkMode Hook:

    • Manages dark mode state using useState.

    • Initializes state from localStorage and updates it on changes.

    • Persists dark mode preference in localStorage.

  2. DarkMode Component:

    • Uses useEffect to add or remove the dark class on the html element based on the dark mode state.

    • Renders an icon that toggles dark mode on click (Moon for light mode, Sun for dark mode).

This setup allows for easy toggling and persistence of dark mode in a React app.

Step 4: Import your Dark Mode in your Header

You probably got a place where to import your Header.jsx — layout, main or App.jsx:

import DarkMode, { useDarkMode } from '@/components/DarkMode';

export default function Header() {
  const [dark, setDark] = useDarkMode();

  return (
    <header className="flex items-center justify-end p-4">
      <DarkMode dark={dark} setDark={setDark} />
    </header>
  );
}

Step 5: Leverage your style in CSS/SCSS

Update your styles to support dark mode:

body {
  @apply text-black min-h-screen dark:bg-slate-900 dark:text-white;
}

EDIT: Shortest version.

// header

import DarkMode from "@/components/DarkMode";

export default function Header() {
  return (
    <header className="flex items-center justify-end pt-4">
      <DarkMode />
    </header>
  );
}

// DarkMode.jsx only (using react-use)
"use client"

import { Moon, Sun } from "lucide-react";
import { useEffect, useState } from 'react';
import { useLocalStorage } from 'react-use';

export default function DarkMode() {
  const [dark, setDark] = useState(false);
  const [value, setValue, remove] = useLocalStorage('dark-mode');

  useEffect(() => {
    if (value) setDark(value);
  }, [value]);

  useEffect(() => {
    if (typeof window !== "undefined") {
      const root = window.document.documentElement;
      if (dark) {
        root.classList.add('dark');
      } else {
        root.classList.remove('dark');
      }
    }
    localStorage.setItem('dark-mode', dark);
  }, [dark]);

  return (
    <div className="cursor-pointer" onClick={() => setDark(!dark)}>
      {dark ? <Sun className="h-[1.2rem] w-[1.2rem]" /> : <Moon className="h-[1.2rem] w-[1.2rem]" />}
    </div>
  );
}

And that's it! You now have a functional dark mode in your React app, using TailwindCSS and lucide-react for a smooth user experience.

Best,

Guillaume Duhan

0
Subscribe to my newsletter

Read articles from CODEWITHG.COM directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

CODEWITHG.COM
CODEWITHG.COM

CTO of Allocations.com, a finance SaaS Miami-based, I am running a YouTube channel: @codewithguillaume. With 15 years of exp. as a freelancer, consultant, and Lead Developer, I have led dozens of engineering teams across Paris, London, and Berlin. I am father of 2 and I live in Dubai.