Dark Mode in React: A Modern Approach with Context & Tailwind

Published at: October 24, 2021

Dark Mode is no longer just a trendy feature – it’s an essential option for many users. Whether it’s to reduce eye strain or to simply match personal preferences, implementing Dark Mode in a React app has become a must-have feature. In this post, we’ll go over how to build a dark mode switcher in React using useContext, and style it beautifully with Tailwind CSS. Plus, we’ll store users’ theme preferences in local storage, so their choice persists across sessions.


1. Why Dark Mode?

Dark Mode isn’t just about aesthetics; it’s about accessibility and improving the overall user experience. It’s an effective way to reduce eye strain, particularly when using apps at night or in low-light conditions. As more apps and websites offer this option, it has become a critical feature for modern apps.


2. Setting Up Tailwind CSS

Before we dive into building the Dark Mode switcher, let’s first ensure that Tailwind CSS is set up in your React app. If you haven’t already done so, follow these steps:

Step 1: Install Tailwind CSS

Start by installing Tailwind CSS with the following steps:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init

This will create a tailwind.config.js file. Next, create a postcss.config.js file:

touch postcss.config.js

Then add the following configuration:

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Step 2: Set Up Tailwind in Your CSS

Open the src/index.css file (or create one if it doesn’t exist) and include the following base Tailwind CSS imports:

@tailwind base;
@tailwind components;
@tailwind utilities;

Tailwind is now set up, and you can use its utility classes to style your app.


3. Building the Theme Switcher with Context

The first step in building a Dark Mode switcher is to manage the theme state. We’ll use React Context to store the current theme and make it accessible across the entire app.

Step 1: Create a ThemeContext

Create a ThemeContext.js file in your src folder:

// src/ThemeContext.js
import React, { createContext, useState, useEffect } from 'react';

export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  // Get the theme from localStorage or default to 'light'
  const savedTheme = localStorage.getItem('theme') || 'light';
  
  // Set the initial theme state
  const [theme, setTheme] = useState(savedTheme);

  // Update localStorage whenever the theme changes
  useEffect(() => {
    localStorage.setItem('theme', theme);
    document.documentElement.className = theme; // Apply theme to the <html> tag for global styles
  }, [theme]);

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

Step 2: Wrapping Your Application with ThemeProvider

Now, open your src/index.js file and wrap your entire app with the ThemeProvider:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ThemeProvider } from './ThemeContext';  // Import ThemeProvider

ReactDOM.render(
  <ThemeProvider>
    <App />
  </ThemeProvider>,
  document.getElementById('root')
);

4. Building the Theme Switcher Button

Next, let’s create a simple button that toggles between light and dark modes. You can use a simple button or a toggle switch to allow the user to change the theme.

Step 1: Create the Button Component

Create a ThemeSwitcher.js component:

// src/ThemeSwitcher.js
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

const ThemeSwitcher = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={toggleTheme}
      className="p-2 bg-blue-500 text-white rounded-md"
    >
      Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
    </button>
  );
};

export default ThemeSwitcher;

5. Styling with Tailwind CSS

Tailwind makes it easy to add styles to our app. Let’s define some global styles that change based on the theme:

Step 1: Add Tailwind Classes for Themes

Modify your src/index.css or global stylesheet to handle the light and dark modes:

/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Light Mode */
html.light {
  --bg-color: #ffffff;
  --text-color: #000000;
}

/* Dark Mode */
html.dark {
  --bg-color: #121212;
  --text-color: #ffffff;
}

/* Apply dynamic styles */
body {
  background-color: var(--bg-color);
  color: var(--text-color);
}

Now, based on whether the user selects “Light” or “Dark”, the appropriate classes will be applied to the root <html> element, and the styles will adjust accordingly.


6. Persisting Theme Preferences with Local Storage

We’re using localStorage in the ThemeContext to store the user’s theme choice. This ensures that the user’s preferred theme persists even after they refresh the page or close the browser.

In the ThemeProvider, we’re getting the theme from localStorage when the app loads:

const savedTheme = localStorage.getItem('theme') || 'light';

Then, when the user toggles the theme, we update both the React state and localStorage:

useEffect(() => {
  localStorage.setItem('theme', theme);
  document.documentElement.className = theme; // Apply theme to <html>
}, [theme]);

7. Putting It All Together

Finally, you can use the ThemeSwitcher component in your main App.js:

// src/App.js
import React from 'react';
import ThemeSwitcher from './ThemeSwitcher';

const App = () => {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <h1 className="text-3xl font-bold">React Dark Mode</h1>
      <ThemeSwitcher />
    </div>
  );
};

export default App;

8. Conclusion

In this post, we’ve built a simple Dark Mode switcher in React using useContext to manage the theme state, Tailwind CSS to style the app, and localStorage to persist the theme preference across sessions. With this approach, users will enjoy a seamless and customizable experience while using your app. By leveraging modern React patterns and Tailwind’s utility-first CSS, we’ve created a maintainable and scalable solution.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *