Front-end

Building a Seamless Light/Dark Theme Toggle with Local Storage

Learn how to build a smooth, CSS-variable-driven light and dark mode toggle that remembers user preferences using JavaScript and localStorage.

Modern web development demands responsive, user-friendly interfaces. Giving users the option to switch to a dark theme reduces eye strain and looks sleek. In this walkthrough, we will build a smooth, CSS-variable-driven theme toggle that remembers the user's preference across page loads using localStorage.

Light Dark

Interactive Demo

Flip the switch to toggle the theme. Refresh the page, and notice how it remembers your choice!

Implementation Details

To keep things modular and prevent messy overrides, this technique relies entirely on CSS Custom Properties (Variables). We define our colors at the top level, and when the user toggles the switch, we simply apply a .dark-mode class that redefines those variables. The browser handles the rest natively.

1. The HTML Structure

We use a standard hidden checkbox wrapped in a label. The slider span acts as the visual switch. We wrap our content inside a main container (theme-demo-wrapper) that will receive our dark mode class.

HTML
<div class="theme-demo-wrapper" id="liveDemoWrapper">
  <div class="toggle-container">
    <span>Light</span>
    <label class="switch">
      <input type="checkbox" id="demoToggleSwitch">
      <span class="slider"></span>
    </label>
    <span>Dark</span>
  </div>
  <div class="demo-card">
    <h4>Demo Card Component</h4>
  </div>
</div>

2. The CSS (Variables & Transitions)

Here, we declare our base light colors. When the .dark-mode class is added via JavaScript, it replaces the variable values. Applying a transition to the background color ensures the theme fades in smoothly rather than flashing instantly.

CSS
/* Default Light Mode Variables */
.theme-demo-wrapper {
  --demo-bg: #f8f9fa;
  --demo-text: #212529;
  --demo-card-bg: #ffffff;
  
  background-color: var(--demo-bg);
  color: var(--demo-text);
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* Dark Mode Variable Overrides */
.theme-demo-wrapper.dark-mode {
  --demo-bg: #1a1b1e;
  --demo-text: #e9ecef;
  --demo-card-bg: #2c2e33;
}

/* Switch styling & animation */
.switch { position: relative; display: inline-block; width: 46px; height: 24px; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .3s; border-radius: 24px; }
.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }

/* Animate the slider when checked */
input:checked + .slider { background-color: #3b82f6; }
input:checked + .slider:before { transform: translateX(22px); }

3. The JavaScript & Local Storage

This is where the real magic happens. If a user chooses dark mode, they expect it to stay dark when they click to another page. We achieve this using the browser's localStorage API.

  • On Load: We check localStorage.getItem() to see if a theme was previously saved. If it was "dark", we apply the class immediately and set the checkbox to true.
  • On Click: We listen for the toggle switch. Depending on its state, we apply/remove the CSS class and use localStorage.setItem() to save the choice for the next visit.
JavaScript
document.addEventListener('DOMContentLoaded', () => {
  const toggleInput = document.getElementById('demoToggleSwitch');
  const demoWrapper = document.getElementById('liveDemoWrapper');

  if (!toggleInput || !demoWrapper) return;

  // 1. Check for a previously saved theme preference
  const currentTheme = localStorage.getItem('theme-preference');
  if (currentTheme === 'dark') {
    demoWrapper.classList.add('dark-mode');
    toggleInput.checked = true; // Visually update the switch
  }

  // 2. Listen for changes and save to local storage
  toggleInput.addEventListener('change', (e) => {
    if (e.target.checked) {
      demoWrapper.classList.add('dark-mode');
      localStorage.setItem('theme-preference', 'dark');
    } else {
      demoWrapper.classList.remove('dark-mode');
      localStorage.setItem('theme-preference', 'light');
    }
  });
});

Need something built?

We design websites for free, animate logos, and build custom software.

Explore our services