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:
- Detect the system’s theme.
- Save and retrieve the user’s theme preference.
- Apply the selected theme to your application.
- 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.
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.
The Legend of Zelda Encyclopedia
$20.00 (as of December 21, 2024 19:39 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)4Pack [Apple MFi Certified] Charger Lightning to USB Charging Cable 6FT Cord Compatible iPhone 14/13/12/11 Pro/11/XS MAX/XR/8/7/6s Plus,iPad Pro/Air/Mini,iPod Touch
$9.99 (as of December 21, 2024 08:38 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)TAKAGI for iPhone Charger, [MFi Certified] Lightning Cable 3PACK 6FT Nylon Braided USB Charging Cable High Speed Transfer Cord Compatible with iPhone 14/13/12/11 Pro Max/XS MAX/XR/XS/X/8/iPad
$9.99 (as of December 21, 2024 08:38 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)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.
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
.
<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.