Hero image

Dark mode with no-script fallback


Seen many examples on dark/light mode that fully relies on javascript to be enabled. So had to do something about it.

This code has both a light-mode class and a dark-mode class that is set in the <html> tag. So there is no way implemented to unset it back to taking system default once set. For that reason you might think twice before store a selection in localStorage, only in sessionStorage to give the user the choise every time the user comes to your site.

HTML

<div class="test">Test of dark mode with js toggle and no-script fallback</div>
<button onclick="toggleMode()">Toggle mode</button>

CSS (SCSS)

@media (prefers-color-scheme: dark) {
  :root {
    --bg-color: #666;
    --box-bg-color: #333;
    --text-color: #ccc;
  }
}
@media (prefers-color-scheme: light) {
  :root {
    --bg-color: #fff;
    --box-bg-color: #666;
    --text-color: #ccc;
  }
}
:root {
  &.light-mode {
    --bg-color: #fff;
    --box-bg-color: #eee;
    --text-color: #333;
  }
  &.dark-mode {
    --bg-color: #666;
    --box-bg-color: #333;
    --text-color: #ccc;
  }
}

body {
  background-color: var(--bg-color);
}

.test {
  background-color: var(--box-bg-color);
  color: var(--text-color);
  padding: 2rem;
}

Typescript

type Modes = 'light-mode' | 'dark-mode'

function toggleMode() {
  /* use sessionStorage for persistance in a MPA app. */
  const rootEl = document.documentElement
  if (!rootEl) return
  const isSystemDarkMode = window.matchMedia(
    '(prefers-color-scheme: dark)'
  ).matches
  rootEl.classList.contains('dark-mode') ||
  (!rootEl.classList.contains('light-mode') && isSystemDarkMode)
    ? switchClass('dark-mode', 'light-mode')
    : switchClass('light-mode', 'dark-mode')

  function switchClass(remove: Mode, add: Mode) {
    rootEl.classList.remove(remove)
    rootEl.classList.add(add)
    rootEl.style.colorScheme = add.split('-')[0]
  }
}

Check out my code at CodePen.io

Favicon bonus tip!

Make your favicon as a SVG file and you can have it adapt to dark/light mode with one media query css.

<svg
  enable-background="new 0 0 122.88 122.88"
  viewBox="0 0 122.88 122.88"
  xmlns="http://www.w3.org/2000/svg"
>
  <path
    d="m61.44 0c8.32 ...truncated endless numbers for your convenience... 23.81 10.92 33.03z"
  />
  <style>
    @media (prefers-color-scheme: dark) {
      :root {
        filter: invert(100%);
      }
    }
  </style>
</svg>