Dark/Light Mode Toggle
By Amr
Theme color mode switcher supporting light, dark, and auto modes with system preference detection.
Estimated reading time: 5 minutes
Table of Contents
Dark/Light Mode Toggle
The Zer0-Mistakes theme includes a color mode switcher supporting light, dark, and automatic system preference detection.
Overview
- Three Modes: Light, dark, and auto
- System Detection: Respects
prefers-color-scheme - Persistent: Saves preference in localStorage
- Bootstrap 5.3: Uses native
data-bs-themeattribute
How It Works
Theme Application
The theme is applied via the data-bs-theme attribute on <html>:
<html data-bs-theme="dark">
Bootstrap 5.3+ automatically adjusts all component colors based on this attribute.
Mode Detection
const getPreferredTheme = () => {
const stored = localStorage.getItem('theme');
if (stored) return stored;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
};
Implementation
JavaScript
(() => {
'use strict';
const getStoredTheme = () => localStorage.getItem('theme');
const setStoredTheme = theme => localStorage.setItem('theme', theme);
const getPreferredTheme = () => {
const storedTheme = getStoredTheme();
if (storedTheme) return storedTheme;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
};
const setTheme = theme => {
if (theme === 'auto') {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute('data-bs-theme', prefersDark ? 'dark' : 'light');
} else {
document.documentElement.setAttribute('data-bs-theme', theme);
}
};
// Apply theme immediately (before DOM ready)
setTheme(getPreferredTheme());
// Handle theme toggle clicks
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value');
setStoredTheme(theme);
setTheme(theme);
});
});
});
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (getStoredTheme() === 'auto' || !getStoredTheme()) {
setTheme('auto');
}
});
})();
Toggle UI
<div class="dropdown">
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">
<i class="bi bi-circle-half"></i>
<span class="visually-hidden">Toggle theme</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button class="dropdown-item" data-bs-theme-value="light">
<i class="bi bi-sun me-2"></i> Light
</button>
</li>
<li>
<button class="dropdown-item" data-bs-theme-value="dark">
<i class="bi bi-moon me-2"></i> Dark
</button>
</li>
<li>
<button class="dropdown-item" data-bs-theme-value="auto">
<i class="bi bi-circle-half me-2"></i> Auto
</button>
</li>
</ul>
</div>
Customization
Custom Colors
Override Bootstrap CSS variables:
[data-bs-theme="dark"] {
--bs-body-bg: #1a1a2e;
--bs-body-color: #eaeaea;
--bs-primary: #4f46e5;
}
[data-bs-theme="light"] {
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
--bs-primary: #3b82f6;
}
Code Block Themes
[data-bs-theme="dark"] pre {
background-color: #1e1e1e;
}
[data-bs-theme="light"] pre {
background-color: #f8f9fa;
}
Images
Swap images based on theme:
<picture>
<source srcset="logo-dark.png" media="(prefers-color-scheme: dark)">
<img src="logo-light.png" alt="Logo">
</picture>
Or with CSS:
[data-bs-theme="dark"] .logo {
content: url('logo-dark.png');
}
Storage
localStorage Key
Theme preference stored as:
localStorage.setItem('theme', 'dark'); // 'light', 'dark', or 'auto'
Clear Preference
localStorage.removeItem('theme');
Transitions
Smooth Theme Change
html {
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Disable during page load */
html.no-transition,
html.no-transition * {
transition: none !important;
}
// Disable transitions during initial load
document.documentElement.classList.add('no-transition');
setTheme(getPreferredTheme());
requestAnimationFrame(() => {
document.documentElement.classList.remove('no-transition');
});
Accessibility
ARIA Labels
<button aria-label="Switch to dark mode">
<i class="bi bi-moon"></i>
</button>
Current State
<button aria-pressed="true" data-bs-theme-value="dark">
Dark
</button>
Reduced Motion
@media (prefers-reduced-motion: reduce) {
html {
transition: none;
}
}
Troubleshooting
Flash of Wrong Theme
Ensure theme script runs before body:
<head>
<script src="/assets/js/color-modes.js"></script>
</head>
Theme Not Persisting
- Check localStorage access
- Verify script is setting storage
- Test in private browsing
Bootstrap Components Not Theming
Ensure using Bootstrap 5.3+:
<!-- Required for data-bs-theme support -->
<link href="bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">