How to implement theme switching in JavaScript

In this article, you will learn how to JavaScriptbuild a theme switcher in . This should be a no-brainer, but you might also learn something from my code.

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 ​​are darklightor system. 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( darklight), 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 ​​are darkor light.
  • options.defaultMode: Used to restore correct theme preferences. For example, you can localStoragesave 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 statechanges 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 helperfunctions:

  • getSystemTheme(): This function returns the current OS theme darkorlight
  • getThemeByMode()dark: This function returns or based on our theme mode light. For example, if mode is set to dark, then returned dark. However, when the mode is set to system, we check the system theme and, depending on the operating system’s preferences, return a value of dark.light

This code will not appear in our createThemeStore()function. We will use it window.matchMediawith prefers-color-schememedia 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 JavaScriptimplementation, you can use it anywhere. I’ll Reactdemonstrate 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 Reactcode examples for manually switching topic modes and demonstrated in .