
Posted on • Originally published atpavelkeyzik.com
How to implement Theme Switcher in JavaScript
In this article, you'll learn how to build a theme switcher in JavaScript. It should be a pretty easy thing to do, but you might learn something from my code as well. Let's have fun.
What cases do we need to cover?
One of the most basic scenarios we should address is changing the theme from light to dark and vice versa. The second thing we need to remember is that some people prefer to use the same settings as in the system. It's useful for those who switch between dark and light themes throughout the day. The third thing is saving user preferences; otherwise, if you refresh the page, all the settings will be set to default again.
Create a theme store
Our initial function will becreateThemeStore()
, which will contain nearly everything. I want to mention that this may not be the optimal approach, but hey, we're coding for fun here.
functioncreateThemeStore(options){// Initial modeconstinitialMode=options.defaultMode||'system'// Initial stateconststate={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, with possible values ofdark
,light
, orsystem
. It allows us to determine whether to use the system's theme or not.systemTheme
: This holds the current value of the theme in your OS. Even if we choose a specific theme (dark
orlight)
, we still update this variable when the OS theme changes to ensure we can adjust the theme correctly when the user switches tosystem
mode.theme
: This is the actual theme that the user sees, with possible values ofdark
orlight
.options.defaultMode
: This is used to restore correct theme preferences. For instance, you could save theme changes inlocalStorage
and then use it as the default, ensuring that the user's preferences are retained.
Add subscriptions
When the user changes the theme or the OS theme is updated, we need a way to notify our code. This is where subscriptions come in. We need to allow subscriptions to changes in ourstate
object. Here's the code that will help us with it. Remember, for now, we do everything insidecreateThemeStore()
.
functioncreateThemeStore(options){// ...// Create subscriptions object to be able notify subscribersconstsubscriptions=newMap()letsubscriptionId=0// Just a unique id for every subscriber// A place where we send notification to all of our subscribersfunctionnotifyAboutThemeChange(theme){subscriptions.forEach((notify)=>{constnotificationData={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 changesfunctionsubscribe(callback){subscriptionId++subscriptions.set(subscriptionId,callback)state.systemTheme=getSystemTheme()// We'll define it laterif(state.mode==='system'){notifyAboutThemeChange(state.systemTheme)}else{notifyAboutThemeChange(state.theme)}returnsubscriptionId}// A function that allows to unsubscribe from changesfunctionusubscribe(subscriptionId){subscriptions.delete(subscriptionId)}return{subscribe,usubscribe,}}
Here's how it works from the consumer side.
// Create a theme storeconststore=createThemeStore()// Suscribe to changesconstsubscriptionId=store.subscribe((newTheme)=>{// Here you'll be seeing theme changesconsole.log(newTheme)})// When you need to unsubscribe from theme change, you just callstore.usubscribe(subscriptionId)
Detect a system theme preferences
Now that we have our base code structure, let's add something useful. We need to define two helper functions:
getSystemTheme()
: This should return the current OS theme dark or lightgetThemeByMode()
: This should return either dark or light based on our theme mode. For example, if the mode is set to dark, we return dark. However, when the mode is set to system, we check the system theme and respond with either dark or light, depending on the OS preferences.
It's important to note that this code won't be inside ourcreateThemeStore()
function. We're usingwindow.matchMedia
with aprefers-color-scheme
media query, allowing us to determine if the OS system is set to dark or not.
constmediaQuery='(prefers-color-scheme: dark)'// Get's current OS systemfunctiongetSystemTheme(){if(window.matchMedia(mediaQuery).matches){return'dark'}return'light'}// Based on user's preferences we return correct themefunctiongetThemeByMode(mode){if(mode==='system'){returngetSystemTheme()}returnmode}functioncreateThemeStore(options){// ...}
Now, the only thing we need to do to detect our OS theme changes is to add event listener.
functioncreateThemeStore(options){// ...// When the OS preference has changedwindow.matchMedia(mediaQuery).addEventListener('change',(event)=>{constprefersDarkMode=event.matches// We change system themestate.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 changedif(state.mode==='system'){notifyAboutThemeChange(state.systemTheme)}})}
Add an ability to manually change the theme mode
We've implemented automatic theme updates whenever our OS preferences change. The only part we haven't discussed yet is manual updates of the theme mode. You'll be using this function on your dark, light, and system theme buttons.
functioncreateThemeStore(options){// ...functionchangeThemeMode(mode){constnewTheme=getThemeByMode(mode)state.mode=modestate.theme=newThemeif(state.mode==='system'){// If the mode is system, send user a system themenotifyAboutThemeChange(state.systemTheme)}else{// Otherwise use the one that we've selectednotifyAboutThemeChange(state.theme)}}return{subscribe,usubscribe,changeThemeMode,}}
Usage example
Our code is pure JavaScript, and you can use it anywhere. I'll demonstrate an example in React, but feel free to try it in any framework or library you enjoy.
// Create a theme store from saved theme mode// or use `system` if user hasn't changed preferencesconststore=createThemeStore({defaultMode:localStorage.getItem("theme")||"system",});functionMyComponent(){// Initial active theme is `null` here, but you could use the actual valueconst[activeTheme,setActiveTheme]=useState(null)useEffect(()=>{// Subscribe to our store changesconstsubscriptionId=store.subscribe((notification)=>{// Update themesetActiveTheme(notification.theme)// Save selected theme mode to localStoragelocalStorage.setItem('theme',notification.mode)})return()=>{store.usubscribe(subscriptionId)}},[])return(<><p>Activetheme:<b>{activeTheme}</b></p><p>Changethemeto:</p><buttononClick={()=>store.changeThemeMode("dark")}>Dark</button><buttononClick={()=>store.changeThemeMode("light")}>Light</button><buttononClick={()=>store.changeThemeMode("system")}>System</button><>)}
Thank you!
I appreciate that you joined me on this journey, and if you were able to make it work, I'm proud of you! If something doesn't work for you or if you want to find the entire code, you can locate ithere.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse