Skip to content

Instantly share code, notes, and snippets.

@jack3898
Last active January 26, 2025 19:48
Show Gist options
  • Save jack3898/bad8342395a8eab9f686dbfc22f42566 to your computer and use it in GitHub Desktop.
Save jack3898/bad8342395a8eab9f686dbfc22f42566 to your computer and use it in GitHub Desktop.
Alternative to Shadcn's theme provider

Improvements

  • Migration away from context to a global Zustand store for improved performance with selectors
  • Less reliance on useEffect and cleanup functions. Events are registered at the module level
  • Adds reactivity to system preference, changing preference in the OS will trigger a theme change in the browser if the user set their preference to "system"
  • Uses Zustand persist middleware for effortless localStorage synchronisation with state

Usage:

const theme = useThemeStore((state) => state.getTheme()); // "light" or "dark"

To set the theme:

const setPreference = useThemeStore((state) => state.setPreference) // setPreference(preference) where preference is "light", "dark" or "system"
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
export type ThemeMode = "dark" | "light";
export type UserThemeMode = ThemeMode | "system";
export type ThemeStore = {
userPreference: UserThemeMode;
systemPreference: ThemeMode;
setPreference: (preference: UserThemeMode) => void;
getTheme: () => ThemeMode;
};
const systemDarkMode = window.matchMedia("(prefers-color-scheme: dark)");
export const useThemeStore = create<ThemeStore>()(
persist(
(set, get): ThemeStore => ({
userPreference: "system",
systemPreference: systemDarkMode.matches ? "dark" : "light",
getTheme: (): ThemeMode => {
const { userPreference, systemPreference } = get();
if (userPreference === "system") {
return systemPreference;
} else {
return userPreference;
}
},
setPreference: (preference): void => set({ userPreference: preference }),
}),
{
name: "app-theme",
storage: createJSONStorage(() => localStorage),
},
),
);
function setRootClass(theme: ThemeMode): void {
const root = window.document.documentElement;
root.classList.remove("light", "dark");
root.classList.add(theme);
}
// Ensures the root class is set on initial page load
setRootClass(useThemeStore.getState().getTheme());
useThemeStore.subscribe((store) => {
setRootClass(store.getTheme());
});
systemDarkMode.addEventListener("change", (event) => {
useThemeStore.setState({
systemPreference: event.matches ? "dark" : "light",
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment