ADVERTISEMENT
Google AdSense Slot (headerLeaderboard)

Set your Publisher ID in src/config/ads.ts to display live ads.

How-To: 50+ Real-World Developer Recipes• 7 min read

How To: Build a Dark Mode Toggle

Implement a flicker-free dark mode toggle using CSS Custom Properties, localStorage caching, and operating system preference detection.

The Three Pillars of a Rock-Solid Dark Mode System

A professional dark mode system consists of three coordinated layers:

1CSS Custom Properties (Variables): Defining your colors as semantic variables (--bg-primary, --text-primary, --border-color) scoped to the :root and html.dark selectors.
2User Preference Storage: Remembering the user's explicit choice in localStorage so their chosen theme persists across page reloads and future visits.
3Operating System Preference Sync: Respecting the user's OS-level theme via @media (prefers-color-scheme: dark) as the intelligent default when no explicit preference has been saved yet.

Preventing the Infamous White Flash (FOUT)

A common bug in beginner implementations is the "Flash of Unstyled Theme" (FOUT) — where a dark mode user opens the page and sees a blinding white flash for 200ms before JavaScript initializes.

To prevent this, place an inline blocking script directly inside the document <head> BEFORE the <body> renders. Because it runs synchronously, the .dark class is attached before the DOM is painted on the screen.

Head-Level Blocking Theme Resolver
<!-- Place directly in HTML <head> -->
<script>
  (function() {
    const saved = localStorage.getItem('theme');
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

    if (saved === 'dark' || (!saved && prefersDark)) {
      document.documentElement.classList.add('dark');
    } else {
      document.documentElement.classList.remove('dark');
    }
  })();
</script>

The Interactive Toggle Switch Handler

When the user clicks the theme toggle button, toggle the .dark class on document.documentElement and write the new state into localStorage.

Toggle Handler Function
function toggleDarkMode() {
  const isDark = document.documentElement.classList.toggle('dark');
  localStorage.setItem('theme', isDark ? 'dark' : 'light');

  // Update ARIA accessibility state
  const btn = document.getElementById('theme-toggle-btn');
  if (btn) {
    btn.setAttribute('aria-pressed', isDark ? 'true' : 'false');
  }
}
Pro Tip

You can also attach an event listener to window.matchMedia("(prefers-color-scheme: dark)") so that if the user changes their OS theme while your tab is open, your site adapts dynamically.

ADVERTISEMENT
Google AdSense Slot (inContentBanner)

Set your Publisher ID in src/config/ads.ts to display live ads.

Test Yourself With Exercise

+15 XP

Where should the theme initialization script be placed to eliminate the white flash on page load?