Skip to Content
All memories

Dark Mode Toggle

 — #Theming#Dark Mode

Dark mode toggle without the flash of default theme. Important bits:

  • CSS variables for color theming
  • Put data-theme attribute on <html>, not <body>, so we can run the JS before the DOM finishes rendering
  • Run local storage check in the <head>
  • JS for toggle button click handler can come after render


<!DOCTYPE html>
<html lang="en" data-theme="light">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      // If there's a theme stored in localStorage, use it on the <html>
      const localStorageTheme = localStorage.getItem('theme');
      if (localStorageTheme) {
        document.documentElement.setAttribute('data-theme', localStorageTheme);
    <div class="theme-toggle">
        class="theme-toggle-btn js-theme-toggle"
        aria-label="Activate dark mode"
        title="Activate dark mode"
        <svg class="light-mode">
          <use xlink:href="#sun"></use>
        <svg class="dark-mode">
          <use xlink:href="#moon"></use>

    <script src="app.js"></script>

CSS Variables

:root {
  --bg: #ffffff;
  --text: #000000;

[data-theme='dark'] {
  --bg: #000000;
  --text: #ffffff;


const themeToggleBtn = document.querySelector('.js-theme-toggle');

themeToggleBtn.addEventListener('click', () => onToggleClick());

const onToggleClick = () => {
  const { theme } = document.documentElement.dataset;
  const themeTo = theme && theme === 'light' ? 'dark' : 'light';
  const label = `Activate ${theme} mode`;

  document.documentElement.setAttribute('data-theme', themeTo);
  localStorage.setItem('theme', themeTo);

  themeToggleBtn.setAttribute('aria-label', label);
  themeToggleBtn.setAttribute('title', label);