In this article, you will learn how to JavaScript
build a theme switcher in . This should be a no-brainer, but you might also learn something from my code.
Contents
What scenarios do we need to handle?
- One of the most basic scenarios we should tackle is changing the subject from light to dark or vice versa .
- The second thing we need to address is that some people prefer to use the same settings as in the system . This is useful for those who switch between dark and light themes throughout the day.
- The third thing is to save user preferences , otherwise all settings will be set to default again after refreshing the page.
Create theme store
We create the initial function createThemeStore()
, which will contain almost everything. We do it this way here, but it may not be the best approach.
function createThemeStore ( options ) { // Initial mode const initialMode = options. defaultMode || 'system' // Initial state const state = { mode : initialMode, systemTheme : getSystemTheme (), theme : getThemeByMode (initialMode), } }
Here we create a state with only 3 variables:
mode
: This represents the selected mode of the theme, possible values aredark
,light
orsystem
. It allows us to decide whether to use the system’s theme or not.systemTheme
: It holds the value of the current operating system theme. Even if we select a specific theme(dark
slight
), we will still update this variable when the operating system theme changes to ensure that we adjust the theme correctly when the user switches to system mode.theme
: This is the actual topic that the user sees, possible values aredark
orlight
.options.defaultMode
: Used to restore correct theme preferences. For example, you canlocalStorage
save theme changes in and then use them as defaults, ensuring that users’ preferences are preserved.
Add subscription
We need a way to notify our code when the user changes the topic or the OS topic is updated, this is where using subscriptions comes in, we need to allow state
changes in the subscription object. The code below will help us do it, remember now we createThemeStore()
do everything in .
function createThemeStore ( options ) { // ... // Create subscriptions object to be able notify subscribers const subscriptions = new Map () let subscriptionId = 0 // Just a unique id for every subscriber // A place where we send notification to all of our subscribers function notifyAboutThemeChange ( theme ) { subscriptions. forEach ( ( notify ) => { const notificationData = { mode : state. mode , theme, } notify (notificationData) // Calls subscribed function (The example how we use it will be later) }) } // A function that allows to subscribe to state changes function subscribe ( callback ) { subscriptionId++ subscriptions.set ( subscriptionId , callback) state. systemTheme = getSystemTheme () // We'll define it later if (state. mode === 'system' ) { notifyAboutThemeChange (state. systemTheme ) } else { notifyAboutThemeChange (state. theme ) } return subscriptionId } // A function that allows to unsubscribe from changes function usubscribe ( subscriptionId ) { subscriptions.delete ( subscriptionId ) } return { subscribe, usubscribe, } }
How to use:
// Create a theme store const store = createThemeStore () // Suscribe to changes const subscriptionId = store. subscribe ( ( newTheme ) => { // Here you'll be seeing theme changes console . log (newTheme) }) // When you need to unsubscribe from theme change, you just call store. usubscribe (subscriptionId)
Detect system theme preferences
Now that we have the basic code structure, let’s define two more helper
functions:
getSystemTheme()
: This function returns the current OS themedark
orlight
getThemeByMode()
dark
: This function returns or based on our theme modelight
. For example, if mode is set todark
, then returneddark
. However, when the mode is set to system, we check the system theme and, depending on the operating system’s preferences, return a value ofdark
.light
This code will not appear in our createThemeStore()
function. We will use it window.matchMedia
with prefers-color-scheme
media queries to confirm the current system’s theme value.
const mediaQuery = '(prefers-color-scheme: dark)' // Get's current OS system function getSystemTheme () { if ( window . matchMedia (mediaQuery). matches ) { return 'dark' } return 'light' } // Based on user's preferences we return correct theme function getThemeByMode ( mode ) { if (mode === 'system' ) { return getSystemTheme () } return mode } function createThemeStore ( options ) { // ... }
Now the only thing we need to do is add an event listener to detect changes to the operating system theme.
function createThemeStore ( options ) { // ... // When the OS preference has changed window . matchMedia (mediaQuery). addEventListener ( 'change' , ( event ) => { const prefersDarkMode = event. matches // We change system theme state. systemTheme = prefersDarkMode ? 'dark' : 'light' // And if user chose `system` mode we notify about the change // in order to be able switch theme when OS settings has changed if (state. mode === 'system' ) { notifyAboutThemeChange (state. systemTheme ) } }) }
Add the ability to manually change theme modes
We have now implemented automatic updates of themes whenever our operating system preferences change. What we haven’t discussed yet is manual updates of theme modes. You will use this feature on your dark, light, and system theme buttons.
function createThemeStore ( options ) { // ... function changeThemeMode ( mode ) { const newTheme = getThemeByMode (mode) state.mode = mode state. theme = newTheme if (state. mode === 'system' ) { // If the mode is system, send user a system theme notifyAboutThemeChange (state. systemTheme ) } else { // Otherwise use the one that we've selected notifyAboutThemeChange (state. theme ) } } return { subscribe, usubscribe, changeThemeMode, } }
Usage example
Our code is pure JavaScript
implementation, you can use it anywhere. I’ll React
demonstrate an example in , but you can try it in any framework or library you like.
// Create a theme store from saved theme mode // or use `system` if user hasn't changed preferences const store = createThemeStore ({ defaultMode : localStorage . getItem ( "theme" ) || "system" , }); functionMyComponent () { // Initial active theme is `null` here, but you could use the actual value const [activeTheme, setActiveTheme] = useState ( null ) useEffect ( () => { // Subscribe to our store changes const subscriptionId = store. subscribe ( ( notification ) => { // Update theme setActiveTheme (notification. theme ) // Save selected theme mode to localStorage localStorage . setItem ( 'theme' , notification. mode ) }) return () => { store.usubscribe ( subscriptionId) } }, []) return ( <> < p > Active theme: < b > {activeTheme} </ b > </ p > < p > Change theme to: </ p > < button onClick = {() => store.changeThemeMode("dark ")}>Dark </ button > < button onClick = {() => store.changeThemeMode("light")}>Light </ button > < button onClick = {() => store.changeThemeMode("system") }>System </ button > <> ) }
at last
This article explains how to implement theme switching in JavaScript. By creating a topic store and adding subscription functionality to notify callers when the topic changes, we finally added React
code examples for manually switching topic modes and demonstrated in .