Manage Dark and Light Modes in Your React App with Fluent UI

Managing the user interface theme is crucial for enhancing user experience. With the increasing adoption of dark and light modes, providing users with the flexibility to choose their preferred theme has become more important. In this blog post, I’ll walk you through how to detect the system theme, allow users to set their preferred theme (auto, dark, or light), and save their selection using localStorage in a React application with Fluent UI.

Thank me by sharing on Twitter 🙏

Introduction

Themes play a vital role in the look and feel of applications. Users often have preferences for either a light or dark theme, and some prefer to follow the system’s default theme. Using Fluent UI, a popular React component library developed by Microsoft, we can easily implement theme switching in our React applications. In this post, I’ll show you how to:

  1. Detect the system’s theme.
  2. Save and retrieve the user’s theme preference.
  3. Apply the selected theme to your application.
  4. Allow users to switch between themes.

Detecting the System Theme

The first step is to detect whether the user prefers a light or dark theme based on their system settings. This can be done using the window.matchMedia API, which allows us to query the system’s color scheme.

TypeScript
const LIGHT = 'light';
const DARK = 'dark';

const getSystemTheme = () => {
  return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? DARK : LIGHT;
};

Here, I define constants for light and dark themes. The getSystemTheme function uses window.matchMedia to check if the system prefers a dark color scheme and returns the appropriate theme.

Saving and Retrieving User’s Preference

Next, we’ll need to save the user’s theme preference in localStorage so that it persists across sessions. We’ll also retrieve this preference when the application loads.

TypeScript
const AUTO = 'auto';
const THEME_KEY = 'user-theme';

const getStoredTheme = () => {
  return localStorage.getItem(THEME_KEY) || AUTO;
};

const storeTheme = (theme: string) => {
  localStorage.setItem(THEME_KEY, theme);
};

The AUTO constant represents the automatic theme mode, where the application follows the system theme. The getStoredTheme function retrieves the stored theme from localStorage, defaulting to auto if no preference is found. The storeTheme function saves the user’s preference in localStorage.

Applying the Selected Theme

With the ability to detect the system theme and manage user preferences, we now need to apply the selected theme to our application. Fluent UI provides a ThemeProvider component that allows us to switch between themes easily.

TypeScript
import { createTheme, ThemeProvider, webLightTheme, webDarkTheme } from '@fluentui/react-components';
import React, { useEffect, useState } from 'react';

const getCurrentTheme = (storedTheme: string) => {
  if (storedTheme === AUTO) {
    return getSystemTheme();
  }
  return storedTheme;
};

export const useTheme = () => {
  const [theme, setTheme] = useState<string>(getCurrentTheme(getStoredTheme()));

  useEffect(() => {
    const handleSystemThemeChange = (e: MediaQueryListEvent) => {
      if (getStoredTheme() === AUTO) {
        setTheme(e.matches ? DARK : LIGHT);
      }
    };

    const systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    systemThemeMediaQuery.addEventListener('change', handleSystemThemeChange);

    return () => {
      systemThemeMediaQuery.removeEventListener('change', handleSystemThemeChange);
    };
  }, []);

  const updateTheme = (newTheme: string) => {
    storeTheme(newTheme);
    setTheme(getCurrentTheme(newTheme));
  };

  return { theme, updateTheme };
};

const App = ({ children }) => {
  const { theme, updateTheme } = useTheme();

  const appliedTheme = theme === DARK ? webDarkTheme : webLightTheme;

  return (
    <ThemeProvider theme={appliedTheme}>
      {children}
      <button onClick={() => updateTheme(LIGHT)}>Light</button>
      <button onClick={() => updateTheme(DARK)}>Dark</button>
      <button onClick={() => updateTheme(AUTO)}>Auto</button>
    </ThemeProvider>
  );
};

export default App;

In this code snippet, we create a custom useTheme hook that manages the theme state. The getCurrentTheme function determines the theme based on the user’s preference or the system theme if auto is selected. The useTheme hook also listens for changes in the system theme when auto is selected.

The App component uses the useTheme hook to apply the selected theme using Fluent UI’s ThemeProvider. It also provides buttons for users to switch between light, dark, and auto modes.

Allowing Users to Switch Themes

Finally, let’s provide users with a way to switch between themes. We’ve already added buttons in the App component for this purpose. When a user clicks a button, the updateTheme function is called, updating the theme state and storing the new preference in localStorage.

TypeScript
<button onClick={() => updateTheme(LIGHT)}>Light</button>
<button onClick={() => updateTheme(DARK)}>Dark</button>
<button onClick={() => updateTheme(AUTO)}>Auto</button>

These buttons allow users to switch between light, dark, and auto themes easily. The updateTheme function updates the theme state and stores the user’s preference.

Conclusion

In this blog post, we’ve learned how to detect the system’s theme, save and retrieve the user’s theme preference, apply the selected theme using Fluent UI, and allow users to switch between themes. By following these steps, you can enhance the user experience of your React application by providing a seamless and customizable theme management system.

Implementing theme management in your application not only improves user satisfaction but also makes your application more accessible and user-friendly. Whether users prefer a light or dark theme, or want to follow the system’s default, you now have the tools to provide them with the best experience possible.

Share this:

Leave a Reply