Search code examples
javascriptbootstrap-5darkmode

Leverage Bootstrap 5.3 color modes depending on browser's


I understand Bootstrap 5.3 brings his color scheme support and that I can customize the behavior of several main features.

Now it seems the built-in dark theme does not depend on prefers-color-scheme: dark and rather on the explicitely defined bs-theme data attribute. So browser's prefered theme is not taken into account.

Do I need to update <html>'s data-bs-theme with JS at page load and put it in the local storage for example ?

const lsBsTheme = localStorage.getItem("bs-theme")
const bsTheme = lsBsTheme ||
  window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
document.querySelector("html").dataset.bsTheme = bsTheme
if (!lsBsTheme) localStorage.setItem("bs-theme", bsTheme)

Seems laborious. Isn't there any simpler ?


Solution

  • If you want to only consider prefers-color-scheme, you can build Bootstrap with Sass and set the variable $color-mode-type: media-query;

    If the user should also be able to toggle between light and dark and have their preference saved, using localStorage and data attributes would be appropriate (like shown in the question). There isn't any method built-in to the library. The Bootstrap documentation provides sample code for this:

    /*!
     * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
     * Copyright 2011-2023 The Bootstrap Authors
     * Licensed under the Creative Commons Attribution 3.0 Unported License.
     */
    
    (() => {
      'use strict'
    
      const getStoredTheme = () => localStorage.getItem('theme')
      const setStoredTheme = theme => localStorage.setItem('theme', theme)
    
      const getPreferredTheme = () => {
        const storedTheme = getStoredTheme()
        if (storedTheme) {
          return storedTheme
        }
    
        return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
      }
    
      const setTheme = theme => {
        if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
          document.documentElement.setAttribute('data-bs-theme', 'dark')
        } else {
          document.documentElement.setAttribute('data-bs-theme', theme)
        }
      }
    
      setTheme(getPreferredTheme())
    
      const showActiveTheme = (theme, focus = false) => {
        const themeSwitcher = document.querySelector('#bd-theme')
    
        if (!themeSwitcher) {
          return
        }
    
        const themeSwitcherText = document.querySelector('#bd-theme-text')
        const activeThemeIcon = document.querySelector('.theme-icon-active use')
        const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
        const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
    
        document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
          element.classList.remove('active')
          element.setAttribute('aria-pressed', 'false')
        })
    
        btnToActive.classList.add('active')
        btnToActive.setAttribute('aria-pressed', 'true')
        activeThemeIcon.setAttribute('href', svgOfActiveBtn)
        const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
        themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
    
        if (focus) {
          themeSwitcher.focus()
        }
      }
    
      window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
        const storedTheme = getStoredTheme()
        if (storedTheme !== 'light' && storedTheme !== 'dark') {
          setTheme(getPreferredTheme())
        }
      })
    
      window.addEventListener('DOMContentLoaded', () => {
        showActiveTheme(getPreferredTheme())
    
        document.querySelectorAll('[data-bs-theme-value]')
          .forEach(toggle => {
            toggle.addEventListener('click', () => {
              const theme = toggle.getAttribute('data-bs-theme-value')
              setStoredTheme(theme)
              setTheme(theme)
              showActiveTheme(theme, true)
            })
          })
      })
    })()