
Easily implement a dynamic light and dark mode switcher in JavaScript. Supports user preferences, system themes, and Bootstrap's data-bs-theme for seamless styling.
Seamless Light & Dark Mode Switching with JavaScript and Bootstrap
Supporting both light and dark mode is essential for modern web applications. Many users prefer dark mode, and some want the ability to manually switch between themes. A well-implemented theme switcher should:
✔ Remember user preferences (via localStorage
).
✔ Detect system theme settings (auto mode).
✔ Dynamically update UI elements, including icons and active states.
✔ Work well in a responsive navbar.
In this post, I'll walk you through a robust JavaScript theme switcher, using Bootstrap’s data-bs-theme
, localStorage, and a clean dropdown-based UI for easy switching.
1️⃣ Setting the Initial Theme
To ensure a smooth experience, we apply the correct theme as soon as the page loads. Add this snippet inside the <head>
<script>
document.documentElement.setAttribute(
'data-bs-theme',
localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light')
);
</script>
This script:
- Retrieves the user’s saved theme from
localStorage
. - If no preference is set, it defaults to the system setting (
dark
orlight
). - Applies the theme immediately, preventing flickering.
2️⃣ Implementing the Theme Switcher in JavaScript
The core logic is inside a ThemeChecker
class, ensuring a modular and reusable structure.
🏗️ The ThemeChecker Class
class ThemeChecker {
constructor() {
this.themeToggles = document.querySelectorAll('[data-theme-value]');
this.init();
}
toggleIcon(themeIcon) {
if (!themeIcon) return;
document.querySelectorAll('.theme').forEach((switcher) => {
const iconContainer = switcher.querySelector('svg');
if (iconContainer) {
iconContainer.innerHTML = '';
iconContainer.appendChild(themeIcon.cloneNode(true));
}
});
}
applyTheme() {
const theme = localStorage.getItem('theme') || 'auto';
const prefersDark = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches;
const appliedTheme =
theme === 'auto' ? (prefersDark ? 'dark' : 'light') : theme;
document.documentElement.setAttribute('data-bs-theme', appliedTheme);
this.themeToggles.forEach((toggle) => toggle.classList.remove('active'));
const activeToggle = document.querySelector(
`[data-theme-value="${theme}"]`
);
if (activeToggle) {
activeToggle.classList.add('active');
this.toggleIcon(activeToggle.querySelector('svg'));
}
}
handleThemeChange(e) {
e.preventDefault();
const selectedTheme = e.currentTarget.dataset.themeValue;
if (selectedTheme === 'auto') {
localStorage.removeItem('theme');
} else {
localStorage.setItem('theme', selectedTheme);
}
this.applyTheme();
}
listenForSystemChanges() {
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => {
if (!localStorage.getItem('theme')) {
this.applyTheme();
}
});
}
init() {
this.applyTheme();
this.themeToggles.forEach((toggle) => {
toggle.addEventListener('click', (e) => this.handleThemeChange(e));
});
this.listenForSystemChanges();
}
}
document.addEventListener('DOMContentLoaded', () => new ThemeChecker());
🔹 How This Works:
✔ Saves the theme selection in localStorage.
✔ Updates Bootstrap’s data-bs-theme
dynamically.
✔ Listens for system-wide theme changes and applies the correct mode.
✔ Updates the active state of the dropdown items.
3️⃣ Navbar HTML: Theme Switcher Dropdown
Here’s how you can integrate this into your Bootstrap navbar:
<li class="nav-item dropdown theme">
<a
class="dropdown-toggle nav-link"
aria-expanded="false"
data-bs-toggle="dropdown"
href="#"
>
<svg>🌞</svg>
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" data-theme-value="light">
<svg>🌞</svg> Light
</a>
<a class="dropdown-item" href="#" data-theme-value="dark">
<svg>🌙</svg> Dark
</a>
<a class="dropdown-item" href="#" data-theme-value="auto">
<svg>🔄</svg> Auto
</a>
</div>
</li>
✨ Why This Markup?
- Uses Bootstrap's
dropdown-menu
for theme selection. - Icons update dynamically based on the active selection.
- The dropdown menu remains accessible inside the navbar.
4️⃣ Styling the Theme Switcher (CSS)
The dropdown should look clean and work well in a right-aligned navbar. Here’s the CSS:
/* Theme Switcher Styles */
.theme .dropdown-menu .dropdown-item.active {
color: var(--bs-emphasis-color);
background-color: transparent;
}
.theme .dropdown-item:active {
background-color: transparent;
}
.theme .dropdown-menu .dropdown-item .bi-check-lg {
opacity: 0;
}
.theme .dropdown-menu .dropdown-item.active .bi-check-lg {
opacity: 1;
}
.theme .dropdown-menu .dropdown-item:hover {
background-color: #dee2e676;
}
/* Keep dropdown open on hover for larger screens */
@media (min-width: 768px) {
.dropdown:hover > .dropdown-menu {
display: block;
}
.dropdown > .dropdown-toggle:active {
pointer-events: none;
}
/* Align dropdown to the left for right-aligned navbar */
.theme .dropdown-menu {
transform: translateX(-100%);
left: 100%;
}
}
/* Prevent SVG interaction */
.theme .dropdown-menu svg {
pointer-events: none;
}
🎨 Why This CSS?
✔ The dropdown aligns correctly when in a right-aligned navbar.
✔ The checkmark icon (.bi-check-lg
) is hidden unless the item is active.
✔ The hover state adds a smooth visual effect.
✔ On larger screens, the dropdown stays open on hover for better usability.
🚀 Final Thoughts
This lightweight and efficient theme switcher ensures:
- Seamless theme transitions across page loads.
- Auto mode that respects system preferences.
- Bootstrap-friendly styling for a smooth experience.