System Preferences
Modern operating systems let users choose their preferred color scheme—typically light or dark mode. This library can detect that preference and automatically apply the matching theme, creating a seamless experience that respects user choices.
How It Works
The browser exposes the user's preference via the prefers-color-scheme media query. The theme manager provides methods to:
- Check the current preference (
prefersDark(),prefersLight()) - React to changes (
onSystemChange()) - Apply themes based on preference (
applySystem()) - Watch and auto-switch (
watchSystem())
User's OS Theme Manager Your App
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Dark Mode: ON│────────────►│ prefersDark()│─────────────►│ Dark Theme │
└──────────────┘ │ returns true │ │ Applied │
└──────────────┘ └──────────────┘
│ │
│ User switches │ watchSystem()
│ to Light Mode │ detects change
▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Dark Mode:OFF│────────────►│ callback │─────────────►│ Light Theme │
└──────────────┘ │ fires │ │ Applied │
└──────────────┘ └──────────────┘
Basic Usage
import { createTheme, createThemeManager } from '@motioneffector/theme'
const light = createTheme({
name: 'light',
tokens: { background: '#fff', text: '#000' }
})
const dark = createTheme({
name: 'dark',
tokens: { background: '#1a1a1a', text: '#fff' }
})
const manager = createThemeManager({
themes: [light, dark],
defaultTheme: 'light'
})
// Apply once based on current preference
manager.applySystem('light', 'dark')
// Or watch continuously
const unwatch = manager.watchSystem('light', 'dark')
Key Points
Detection requires a browser — In SSR or Node.js environments,
prefersDark()andprefersLight()returnfalse. Theme application still works, but system detection doesn't.applySystem()is one-time — It checks the preference and applies the matching theme once. If the user changes their OS setting afterward, you won't see the change.watchSystem()is continuous — It applies the theme immediately AND watches for changes. Call the returned function to stop watching.Combine with persistence carefully — If you're using
storageKeyfor persistence, decide whether the saved preference or system preference takes priority. See the guide for patterns.
Examples
Check Current Preference
if (manager.prefersDark()) {
console.log('User prefers dark mode')
} else if (manager.prefersLight()) {
console.log('User prefers light mode')
}
Listen for Changes
const unsubscribe = manager.onSystemChange((scheme) => {
console.log(`System changed to: ${scheme}`)
// scheme is 'dark' or 'light'
})
// Stop listening later
unsubscribe()
Apply Once on Load
// Good for apps where user can override the system preference
manager.applySystem('light', 'dark')
Watch Continuously
// Good for apps that should always match the system
const unwatch = manager.watchSystem('light', 'dark')
// Stop watching when component unmounts
onUnmount(() => unwatch())
Three-Way Toggle (Light / Dark / System)
type Preference = 'light' | 'dark' | 'system'
let userPreference: Preference = 'system'
let unwatchSystem: (() => void) | null = null
function setPreference(pref: Preference) {
// Stop watching if we were
unwatchSystem?.()
unwatchSystem = null
if (pref === 'system') {
unwatchSystem = manager.watchSystem('light', 'dark')
} else {
manager.apply(pref)
}
userPreference = pref
}
// Initial setup
setPreference('system')
Related
- Theme Manager — Provides system preference methods
- System Preference Detection Guide — Step-by-step implementation
- API: System Preferences — Full method reference