Overview
The Settings system provides a centralized way to manage application-wide preferences including theme (light/dark mode) and language selection. All settings are persisted to localStorage and automatically synchronized across browser tabs.
Architecture
The settings system consists of two main components:
useSettingsStore.ts : Zustand store for settings state management
Settings.tsx : UI component for managing preferences
useToastStore.ts : Toast notification system for user feedback
Data Models
Settings Types
type Theme = "light" | "dark" ;
type Language = "es" | "en" ;
interface SettingsState {
theme : Theme ;
language : Language ;
toggleTheme : () => void ;
setLanguage : ( lang : Language ) => void ;
}
Settings Store
The settings store manages theme and language preferences with automatic persistence:
import { create } from "zustand" ;
import { persist } from "zustand/middleware" ;
type Theme = "light" | "dark" ;
type Language = "es" | "en" ;
interface SettingsState {
theme : Theme ;
language : Language ;
toggleTheme : () => void ;
setLanguage : ( lang : Language ) => void ;
}
export const useSettingsStore = create < SettingsState >()(\ n persist (
( set , get ) => ({
theme: "light" ,
language: "es" ,
toggleTheme : () =>
set ({
theme: get (). theme === "light" ? "dark" : "light" ,
}),
setLanguage : ( language ) => set ({ language }),
}),
{
name: "settings" ,
}
)
);
The persist middleware automatically saves settings to localStorage under the key settings. This ensures user preferences are maintained across browser sessions.
Usage Examples
Settings Page Implementation
Create a settings page where users can modify their preferences:
import { PageContainer } from "../../shared/components/PageContainer" ;
import { useSettingsStore } from "../../shared/store/useSettingsStore" ;
import { useTranslation } from "react-i18next" ;
const Settings = () => {
const { theme , toggleTheme } = useSettingsStore ();
const { language , setLanguage } = useSettingsStore ();
const { t } = useTranslation ();
return (
< PageContainer title = { t ( "settings" ) } subtitle = { t ( "preferences" ) } >
< div className = "space-y-6 max-w-xl" >
{ /* Dark Mode Toggle */ }
< div className = "flex items-center justify-between" >
< span className = "font-medium dark:text-gray-200" >
{ t ( "darkMode" ) }
</ span >
< button
onClick = { toggleTheme }
className = "px-4 py-2 border rounded dark:text-gray-200"
>
{ theme === "dark" ? "On" : "Off" }
</ button >
</ div >
{ /* Language Selector */ }
< div className = "flex items-center justify-between" >
< span className = "font-medium dark:text-gray-200" >
{ t ( "language" ) }
</ span >
< select
value = { language }
onChange = { ( e ) => setLanguage ( e . target . value as "en" | "es" ) }
className = "border px-3 py-2 rounded dark:text-gray-200"
>
< option value = "es" className = "dark:text-gray-800" >
Español
</ option >
< option value = "en" className = "dark:text-gray-800" >
English
</ option >
</ select >
</ div >
</ div >
</ PageContainer >
);
};
export default Settings ;
Accessing Settings in Components
You can access settings from any component in your application:
import { useSettingsStore } from "../shared/store/useSettingsStore" ;
function ThemeAwareComponent () {
const theme = useSettingsStore (( state ) => state . theme );
const language = useSettingsStore (( state ) => state . language );
return (
< div className = { theme === "dark" ? "dark-mode" : "light-mode" } >
< p > Current theme: { theme } </ p >
< p > Current language: { language } </ p >
</ div >
);
}
Toggle Theme Programmatically
Toggle the theme from any component:
import { useSettingsStore } from "../shared/store/useSettingsStore" ;
function ThemeToggleButton () {
const toggleTheme = useSettingsStore (( state ) => state . toggleTheme );
const theme = useSettingsStore (( state ) => state . theme );
return (
< button onClick = { toggleTheme } >
Switch to { theme === "light" ? "dark" : "light" } mode
</ button >
);
}
Change Language Programmatically
Update the language from any component:
import { useSettingsStore } from "../shared/store/useSettingsStore" ;
function LanguageSwitcher () {
const setLanguage = useSettingsStore (( state ) => state . setLanguage );
return (
< div >
< button onClick = { () => setLanguage ( "en" ) } > English </ button >
< button onClick = { () => setLanguage ( "es" ) } > Español </ button >
</ div >
);
}
Theme Implementation
Dark Mode with Tailwind CSS
The application uses Tailwind CSS for dark mode styling. Apply the theme by adding a class to your root element:
import { useEffect } from "react" ;
import { useSettingsStore } from "./shared/store/useSettingsStore" ;
function AppInitializer () {
const theme = useSettingsStore (( state ) => state . theme );
useEffect (() => {
// Apply theme to document root
if ( theme === "dark" ) {
document . documentElement . classList . add ( "dark" );
} else {
document . documentElement . classList . remove ( "dark" );
}
}, [ theme ]);
return null ;
}
With Tailwind CSS, you can use the dark: prefix to apply styles conditionally: < div className = "bg-white dark:bg-gray-900 text-black dark:text-white" >
This content adapts to the theme
</ div >
Custom Theme Hook
Create a custom hook for more complex theme logic:
import { useEffect } from "react" ;
import { useSettingsStore } from "../shared/store/useSettingsStore" ;
export function useTheme () {
const theme = useSettingsStore (( state ) => state . theme );
const toggleTheme = useSettingsStore (( state ) => state . toggleTheme );
useEffect (() => {
const root = document . documentElement ;
if ( theme === "dark" ) {
root . classList . add ( "dark" );
root . style . colorScheme = "dark" ;
} else {
root . classList . remove ( "dark" );
root . style . colorScheme = "light" ;
}
}, [ theme ]);
return { theme , toggleTheme , isDark: theme === "dark" };
}
Toast Notification System
The application includes a toast notification system for user feedback:
import { create } from "zustand" ;
interface ToastState {
message : string | null ;
show : ( msg : string ) => void ;
hide : () => void ;
}
export const useToastStore = create < ToastState >(( set ) => ({
message: null ,
show : ( msg ) => set ({ message: msg }),
hide : () => set ({ message: null }),
}));
Using Toasts
Display toast notifications throughout your application:
import { useToastStore } from "../shared/store/useToastStore" ;
import { useSettingsStore } from "../shared/store/useSettingsStore" ;
function SettingsWithToast () {
const toggleTheme = useSettingsStore (( state ) => state . toggleTheme );
const showToast = useToastStore (( state ) => state . show );
const handleThemeChange = () => {
toggleTheme ();
showToast ( "Theme changed successfully!" );
};
return < button onClick = { handleThemeChange } > Toggle Theme </ button > ;
}
Toast Component
Implement a toast notification component:
import { useEffect } from "react" ;
import { useToastStore } from "./useToastStore" ;
export function Toast () {
const { message , hide } = useToastStore ();
useEffect (() => {
if ( message ) {
const timer = setTimeout (() => {
hide ();
}, 3000 );
return () => clearTimeout ( timer );
}
}, [ message , hide ]);
if ( ! message ) return null ;
return (
< div className = "fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg" >
{ message }
</ div >
);
}
Integration with i18n
The settings store integrates seamlessly with the internationalization system. When language changes, i18n automatically updates:
import { useEffect } from "react" ;
import { useSettingsStore } from "../shared/store/useSettingsStore" ;
import { useTranslation } from "react-i18next" ;
function LanguageSync () {
const language = useSettingsStore (( state ) => state . language );
const { i18n } = useTranslation ();
useEffect (() => {
i18n . changeLanguage ( language );
}, [ language , i18n ]);
return null ;
}
Learn more about internationalization in the Internationalization documentation.
API Reference
useSettingsStore Methods
Toggles between light and dark theme. Automatically persists the new theme to localStorage.
Sets the application language to either “en” (English) or “es” (Spanish). The change is persisted automatically.
useSettingsStore State
The current theme setting. Defaults to “light”.
The current language setting. Defaults to “es” (Spanish).
useToastStore Methods
Displays a toast notification with the specified message.
Hides the currently displayed toast notification.
useToastStore State
The current toast message being displayed, or null if no toast is active.
Advanced Features
Multiple Themes
Extend the settings store to support more than two themes:
type Theme = "light" | "dark" | "auto" | "high-contrast" ;
interface SettingsState {
theme : Theme ;
setTheme : ( theme : Theme ) => void ;
}
export const useSettingsStore = create < SettingsState >()(\ n persist (
( set ) => ({
theme: "light" ,
setTheme : ( theme ) => set ({ theme }),
}),
{ name: "settings" }
)
);
System Theme Detection
Detect and apply the system theme preference:
import { useEffect } from "react" ;
import { useSettingsStore } from "../shared/store/useSettingsStore" ;
function SystemThemeDetector () {
const setTheme = useSettingsStore (( state ) => state . setTheme );
useEffect (() => {
const mediaQuery = window . matchMedia ( "(prefers-color-scheme: dark)" );
const handleChange = ( e : MediaQueryListEvent ) => {
setTheme ( e . matches ? "dark" : "light" );
};
// Set initial theme
setTheme ( mediaQuery . matches ? "dark" : "light" );
// Listen for changes
mediaQuery . addEventListener ( "change" , handleChange );
return () => mediaQuery . removeEventListener ( "change" , handleChange );
}, [ setTheme ]);
return null ;
}
Additional Settings
Extend the settings store with additional preferences:
interface SettingsState {
theme : Theme ;
language : Language ;
notifications : boolean ;
soundEnabled : boolean ;
fontSize : "small" | "medium" | "large" ;
toggleTheme : () => void ;
setLanguage : ( lang : Language ) => void ;
toggleNotifications : () => void ;
toggleSound : () => void ;
setFontSize : ( size : "small" | "medium" | "large" ) => void ;
}
Best Practices
Centralized Settings Keep all application-wide settings in a single store for easier management and debugging.
Type Safety Use TypeScript types to ensure only valid values are set for theme and language options.
Persistence Leverage Zustand’s persist middleware to automatically save settings to localStorage without manual implementation.
User Feedback Always provide visual feedback (toasts, animations) when settings change to confirm the action to users.
When adding new settings, ensure they have sensible defaults and don’t break the application if localStorage is cleared or unavailable.
Next Steps
Internationalization Learn how the language setting integrates with i18n
User Management Explore how toasts are used in user operations