<main class="card">
  <header class="card__header header">
    <label class="header__selection toggle">
      <input js-toggle class="toggle__input" aria-label="toggle dark mode" type="checkbox">
      <span class="toggle__fake"></span>
      <h1 class="toggle__text">Dark Mode</h1>
    </label>
  </header>
  <section class="card__body body">
    <p class="body__text">The <code>prefers-color-scheme</code> CSS 5 Media Query is used to detect if the user has requested the system to use a light or dark color theme. This Pen demonstrates how to use a theme toggle for both legacy and progressive code. Check <a target="_blank" rel="noopener" href="//caniuse.com/#feat=prefers-color-scheme" aria-label="Can I Use Website">Caniuse</a> for support.</p>
    <p class="body__text">If the theme is overwriten by the toggle, it will be persisted via local storage; else the media query will keep that setting. Press the following button to clear that storage.</p>
  </section>
  <section class="card__body card__body--right body">
    <button type="button" js-clearStorage>Clear Local Storage</button>
  </section>
</main>
@mixin includeLight {
  $base-text-color: 0,0,0;

  --background-color: #eeeeee;
  --background-card-color: #ffffff;
  --background-success-color: #85da85;
  --background-toggle-inactive-color: rgba(#{$base-text-color}, 0);
  --background-toggle-active-color: rgba(#{$base-text-color}, 0.08);
  --foreground-color: rgba(#{$base-text-color}, 0.87);
  --foreground-success-color: #000000;
  --foreground-secondary-color: rgba(#{$base-text-color}, 0.56);
  --focus-color: rgba(#{$base-text-color}, 0.5);
}

@mixin includeDark {
  $base-text-color: 255,255,255;

  --background-color: #212121;
  --background-card-color: #363636;
  --background-success-color: #008000;
  --background-toggle-inactive-color: rgba(#{$base-text-color}, 0);
  --background-toggle-active-color: rgba(#{$base-text-color}, 0.1);
  --foreground-color: rgba(#{$base-text-color}, 0.87);
  --foreground-success-color: #ffffff;
  --foreground-secondary-color: rgba(#{$base-text-color}, 0.56);
  --focus-color: rgba(#{$base-text-color}, 0.5);
}

%focus {
  outline-color: var(--focus-color);
  outline-style: solid;
  outline-width: 2px;
  outline-offset: 2px;
}

html, html.light { @include includeLight }
html.dark { @include includeDark }

@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) {
  html { @include includeLight }
  html.dark { @include includeDark }
}

@media (prefers-color-scheme: dark) {
  html { @include includeDark }
  html.light { @include includeLight }
}

* {
  box-sizing: border-box;
  &:focus {
    @extend %focus;
  }
}

html, body {
  width: 100%;
  height: 100%;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Roboto', sans-serif;
  background-color: var(--background-color);
}

h1 {
  font-size: 20px;
  color: var(--foreground-color);
}

p {
  color: var(--foreground-secondary-color);
  line-height: 1.5;
  margin-bottom: 16px;
}

a {
  color: var(--foreground-color);
  text-transform: uppercase;
}

code {
  display: inline-block;
  padding: 2px 6px;
  background-color: var(--background-toggle-active-color);
  border-radius: 2px;
}

input[type="checkbox"] {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: inherit;
}

.card {
  width: 100%;
  max-width: 600px;
  margin: 8px 16px;
  padding: 16px 24px 8px;
  background-color: var(--background-card-color);
  border-radius: 8px;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
  &__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
  }
  &__body {
    padding-top: 16px;
    &--right {
      display: flex;
      justify-content: flex-end;
      padding-top: 0;
    }
  }
}

.header {
  &__text {
    padding: 8px 0;
    margin-right: 16px;
  }
}

.footer {
  &__p {
    padding: 8px 24px;
    margin: 0 0 16px -24px;
    border-radius: 0 22px 22px 0;
    background-color: var(--background-success-color);
    color: var(--foreground-success-color);
  }
}

.toggle {
  flex: 1;
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  cursor: pointer;
  &__input {
    &:checked {
      ~ .toggle__fake {
        &::after {
          left: 30px;
          background-color: var(--foreground-color);
        }
      }
    }
    &:focus,
    &:not([disabled]):hover {
      ~ .toggle__fake {
        background-color: var(--background-toggle-active-color);
      }
    }
    &:focus-visible {
      ~ .toggle__fake {
        @extend %focus;
      }
    }
  }
  &__fake {
    will-change: background-color;
    position: relative;
    display: inline-block;
    width: 52px;
    height: 24px;
    background-color: var(--background-toggle-inactive-color);
    border: 1px solid var(--foreground-color);
    border-radius: 12px;
    transition: background-color 100ms cubic-bezier(0.4, 0.0, 0.2, 1);
    &::after {
      will-change: left;
      content: '';
      position: absolute;
      top: 50%;
      left: 4px;
      display: block;
      width: 15px;
      height: 15px;
      background-color: var(--background-card-color);
      border: 1px solid var(--foreground-color);
      border-radius: 8px;
      transform: translateY(-50%);
      transition: left 100ms cubic-bezier(0.4, 0.0, 0.2, 1);
    }
  }
  &__text {
    margin-left: 16px;
    color: var(--foreground-color);
  }
}

button {
  appearance: none;
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  margin: 8px 0 16px;
  padding: 8px;
  color: var(--foreground-color);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  background-color: var(--background-color);
  border: 1px solid var(--foreground-color);
  border-radius: 4px;
  cursor: pointer;
}
View Compiled
const themePreference = () => {
  const hasLocalStorage = localStorage.getItem('theme');
  
  let supports = false;
  let theme = undefined;
  
  if (hasLocalStorage === 'light') { theme = 'light'; }
  if (hasLocalStorage === 'dark') { theme = 'dark'; }

  if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
    theme = hasLocalStorage ? hasLocalStorage : 'dark';
    supports = true;
  };
  if (window.matchMedia(`(prefers-color-scheme: light)`).matches) {
    theme = hasLocalStorage ? hasLocalStorage : 'light';
    supports = true;
  };
  if (window.matchMedia(`(prefers-color-scheme: no-preference)`).matches) {
    theme = hasLocalStorage ? hasLocalStorage : 'none';
    supports = true;
  };
  
  return {supports, theme};
}

addFooter = () => {
  const parent = document.querySelector('.card');
  const footer = document.createElement('footer');

  const html = `
    <p class="footer__p"><span aria-label="checkmark:">✔</span> Your system supports the CSS 5 Media Query <code>prefers-color-scheme</code>.</p>
  `;
  
  footer.classList.add('card__footer', 'footer');
  footer.innerHTML = html;
  
  parent.appendChild(footer);
}

document.addEventListener('DOMContentLoaded', e => {
  console.clear();
  
  const userThemePreference = themePreference();
  const toggle = document.querySelector('[js-toggle]');
  const clearStorage = document.querySelector('[js-clearStorage]');
  const html = document.documentElement;
    
  const setTheme = () => {
    switch(userThemePreference.theme) {
      case 'dark':
        toggle.checked = true;
        html.classList.add('dark');
        html.classList.remove('light');
        break;
      case 'light':
        toggle.checked = false;
        html.classList.remove('dark');
        html.classList.add('light');
        break;
    }
  }
  
    
  setTheme();
  
  if (userThemePreference.supports) {
    addFooter();
  }
    
  clearStorage.addEventListener('click', e => {
    localStorage.removeItem('theme');
    console.info('local storage cleared');
  }, false);
  
  toggle.addEventListener('click', e => {
    if (toggle.checked) {
      html.classList.add('dark');
      html.classList.remove('light');
      localStorage.setItem('theme', 'dark');
    } else {
      html.classList.remove('dark');
      html.classList.add('light');
      localStorage.setItem('theme', 'light');
    }
  }, false);
}, false);
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.