Danny 🎠 6 days ago
I LOVE THE NEW DESIGN SO MUCH AAAAAAAAAAH
nick 🤞 6 days ago
the new designs are finally here! still a few rough edges here and there, but we'll smooth them out. if you spot any bugs, tell us <3

Smart dark mode toggle

Written by nick • 07.02.2026

Live Preview

These days, it's pretty much standard to have a dark mode. I personally use it pretty much all the time, so I hardly notice light mode anymore. 👀 Someone on our wishlist asked how to add a toggle for it to a website, so here's a simple way to do it!


1. The basic HTML & CSS

First up, we need a button (yes, shocking, I know). I've set this up so you can easily change what your button says or looks like directly in your HTML, without ever touching the JavaScript later.

<!-- Change the labels below to whatever you want! -->
<button
   id="theme-toggle"
   data-light-label="☀️"
   data-dark-label="🌙"
   aria-label="Toggle Dark Mode">
</button>

A quick heads-up on those labels: See the data-light-label and data-dark-label? Whatever you type there is what will show up on the button. You can use emojis, plain text like "Go Dark", or even icon fonts like Font Awesome!

Important: Keep the id="theme-toggle" exactly as it is! The JavaScript uses this specific name to find the button. If you change it here, you will also have to change it in the JavaScript section.

You'll notice I also added an aria-label. Since our button might only have an emoji (☀️) or an icon inside it, screen readers for visually impaired users might not know what it actually does. The aria-label tells them: "Hey, this button toggles dark mode," making your site a little bit more accessible to everyone.

To make the theme swap actually work, we use CSS variables. Think of these as "nicknames" for your colors. Instead of manually changing dozens of color codes, the browser just updates the "nickname" definition once, and your whole site changes instantly!

If you're starting a brand new project, you can set these up right away. If you're adding the toggle to a site you've already built, you'll just need to go through your CSS and replace your hardcoded colors (like #ffffff) with these variables (like var(--bg-color)).

<style>
  /* 1. Define all your default light mode colors here */
  :root {
    --bg-color: #f4f4f4;
    --text-color: #333333;
    --button-bg: #e0e0e0;
    --button-text: #333333;
  }
   
  /* 2. Override them for your dark mode */
  [data-theme='dark'] {
    --bg-color: #1a1a1a;
    --text-color: #e0e0e0;
    --button-bg: #333333;
    --button-text: #f1f1f1;
  }
   
  /* 3. Use the variables instead of hardcoded hex codes */
  body {
    background-color: var(--bg-color);
    color: var(--text-color);
  }
   
  #theme-toggle {
    background-color: var(--button-bg);
    color: var(--button-text);
    border: none;
    border-radius: 50%;
    width: 50px;
    height: 50px;
    cursor: pointer;
    font-size: 20px;
    transition: all 0.3s ease;
  }
</style>

2. The JavaScript

Now for the part that actually makes it work! This script handles the heavy lifting with some "smart logic." First, it checks the visitor's browser or system settings. If they already use dark mode on their computer, your site will automatically match it on their very first visit! It also takes care of swapping the labels we set up previously so the button always shows the right icon.

You'll notice we are splitting the code into two pieces. The first part goes in your <head> to prevent a "white flash" (where the page starts with light mode for a split second before loading your preference). The second part goes at the very bottom of your <body> to handle the button clicks.

Add this to your <head> section:

<script>
  // The key name used for saving the preference in localStorage
  const storageKey = 'theme-preference';
      
  // Look for a saved theme in the browser or check the user's system settings
  const savedTheme = localStorage.getItem(storageKey);
  const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      
  // Decide which theme to use when the page first loads
  const initialTheme = savedTheme || (systemPrefersDark ? 'dark' : 'light');

  // Apply the correct theme
  document.documentElement.setAttribute('data-theme', initialTheme);
</script>

Add this at the bottom of your <body>:

<script>
  // The toggle button element
  const toggleButton = document.getElementById('theme-toggle');
    
  // The <html> element where the dark mode class is applied
  const htmlElement = document.documentElement;

  // Grab the labels from the data attributes
  const lightLabel = toggleButton.getAttribute('data-light-label');
  const darkLabel = toggleButton.getAttribute('data-dark-label');

  // Apply the correct button icon based on the theme set in head
  const currentTheme = htmlElement.getAttribute('data-theme');
  if (lightLabel && darkLabel) {
    toggleButton.innerHTML = currentTheme === 'dark' ? darkLabel : lightLabel;
  }
      
  // Listen for whenever a user clicks the button to swap themes
  toggleButton.addEventListener('click', () => {
    const currentTheme = htmlElement.getAttribute('data-theme');
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
      
    applyTheme(newTheme);
    localStorage.setItem(storageKey, newTheme);
  });
      
  // Function to update the HTML attribute and change the button's label
  function applyTheme(theme) {
    htmlElement.setAttribute('data-theme', theme);
      
    if (lightLabel && darkLabel) {
      toggleButton.innerHTML = theme === 'dark' ? darkLabel : lightLabel;
      }
  }
</script>

Tip: To make sure the site remembers the user's choice even after they close the tab, the script saves their preference in localStorage. I've included a storageKey variable at the top of the code, so you can easily rename the "save file" if you need to. You probably won't need to touch this unless you're running other scripts that might use the same name!


3. The full code

And that's it! You've now got a smart dark mode toggle that respects what your visitors prefer while still letting them swap things around whenever they want. ✨

To see how everything looks when it's all put together, I've put the complete code below. This version uses the Font Awesome icons we talked about earlier so you can see them in action right away!

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <title>Smart Dark Mode Toggle</title>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
      <style>
        /* Default (light mode) colors */
        :root {
          --bg-color: #f4f4f4;
          --text-color: #333333;
          --button-bg: #e0e0e0;
          --button-text: #333333;
        }
          
        /* Dark mode colors */
        [data-theme='dark'] {
          --bg-color: #1a1a1a;
          --text-color: #e0e0e0;
          --button-bg: #333333;
          --button-text: #f1f1f1;
        }
          
        body {
          background-color: var(--bg-color);
          color: var(--text-color);
          font-family: Arial, sans-serif;
        
          /* These lines below are just to center the demo on the page! */
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          height: 100vh;
          margin: 0;
        }
          
        #theme-toggle {
          background-color: var(--button-bg);
          color: var(--button-text);
          border: none;
          border-radius: 50%;
          width: 50px;
          height: 50px;
          cursor: pointer;
          font-size: 20px;
          transition: all 0.3s ease;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      </style>

      <script>
        // The key name used for saving the preference in localStorage
        const storageKey = 'theme-preference';
          
        // Look for a saved theme in the browser or check the user's system settings
        const savedTheme = localStorage.getItem(storageKey);
        const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
          
        // Decide which theme to use when the page first loads
        const initialTheme = savedTheme || (systemPrefersDark ? 'dark' : 'light');

        // Apply the correct theme
        document.documentElement.setAttribute('data-theme', initialTheme);
      </script>
   </head>
   <body>
   
      <button 
        id="theme-toggle"
        data-light-label='<i class="fa-solid fa-sun"></i>'
        data-dark-label='<i class="fa-solid fa-moon"></i>'
        aria-label="Toggle Dark Mode">
      </button>
       
      <h1>Smart Dark Mode</h1>
      <p>Click the toggle above to see it in action!</p>
       
      <script>
        // The toggle button element
        const toggleButton = document.getElementById('theme-toggle');
        
        // The <html> element where the dark mode class is applied
        const htmlElement = document.documentElement;

        // Grab the labels from the data attributes
        const lightLabel = toggleButton.getAttribute('data-light-label');
        const darkLabel = toggleButton.getAttribute('data-dark-label');

        // Apply the correct button icon based on the theme set in head
        const currentTheme = htmlElement.getAttribute('data-theme');
        if (lightLabel && darkLabel) {
            toggleButton.innerHTML = currentTheme === 'dark' ? darkLabel : lightLabel;
        }
          
        // Listen for whenever a user clicks the button to swap themes
        toggleButton.addEventListener('click', () => {
            const currentTheme = htmlElement.getAttribute('data-theme');
            const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
          
            applyTheme(newTheme);
            localStorage.setItem(storageKey, newTheme);
        });
          
        // Function to update the HTML attribute and change the button's label
        function applyTheme(theme) {
            htmlElement.setAttribute('data-theme', theme);
          
            if (lightLabel && darkLabel) {
                toggleButton.innerHTML = theme === 'dark' ? darkLabel : lightLabel;
            }
        }
      </script>
       
   </body>
</html>

Aaaaand you're done! Your site is now officially ready for the night owls. <3