Bootstrap 5.3 added something I'd wanted for a long time — native dark mode support. No more maintaining two separate stylesheets. No more toggling classes on hundreds of elements. Just set data-bs-theme="dark" on your <html> tag and Bootstrap handles the rest.
Here's how to do it properly, including the toggle, system preference detection and localStorage persistence.
How Bootstrap 5 Dark Mode Works
In Bootstrap 5.3+, every component reads from CSS variables that change based on the data-bs-theme attribute. Light is the default. Set data-bs-theme="dark" on any element and all Bootstrap components inside it switch to dark mode automatically.
That's it at the CSS level. No extra stylesheet, no class switching on every element.
Basic Setup — Site-Wide Dark Mode
<!-- Light mode (default) -->
<html lang="en">
<!-- Dark mode -->
<html lang="en" data-bs-theme="dark">
Every Bootstrap component inside responds: navbar goes dark, cards get dark backgrounds, modals, dropdowns, tables — all of it. One attribute.
Adding a Toggle Switch
Here's the JavaScript to toggle between modes:
// Get current theme
function getTheme() {
return localStorage.getItem('theme') || 'light'
}
// Set theme
function setTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme)
localStorage.setItem('theme', theme)
updateToggle(theme)
}
// Update toggle button appearance
function updateToggle(theme) {
const btn = document.getElementById('themeToggle')
if (!btn) return
btn.textContent = theme === 'dark' ? '☀️ Light Mode' : '🌙 Dark Mode'
}
// Apply saved theme on page load
setTheme(getTheme())
And the HTML toggle button:
<button id="themeToggle" class="btn btn-outline-secondary btn-sm"
onclick="setTheme(getTheme() === 'dark' ? 'light' : 'dark')">
🌙 Dark Mode
</button>
Detecting System Preference
If the user hasn't set a preference yet, default to their system setting:
function getTheme() {
// Check localStorage first
const saved = localStorage.getItem('theme')
if (saved) return saved
// Fall back to system preference
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
}
Also listen for system preference changes while the page is open:
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
// Only update if user hasn't manually set a preference
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light')
}
})
Component-Level Dark Mode
You can apply dark mode to specific sections without affecting the whole page:
<!-- Dark navbar on a light page -->
<nav class="navbar navbar-expand-lg" data-bs-theme="dark" style="background:#0d0d0d;">
<!-- navbar content -->
</nav>
<!-- Dark sidebar -->
<aside data-bs-theme="dark" style="background:#1a1a2e;">
<!-- sidebar content -->
</aside>
This is great for admin dashboards where you might want a dark sidebar but a light main content area.
Customizing Dark Mode Colors
Bootstrap's dark mode uses CSS variables you can override:
[data-bs-theme="dark"] {
--bs-body-bg: #0a0a0a; /* main background */
--bs-body-color: #e8e8e8; /* main text color */
--bs-card-bg: #1a1a2e; /* card background */
--bs-border-color: #2d2d2d; /* border color */
--bs-secondary-bg: #111827; /* secondary backgrounds */
}
Put this in your main CSS file after importing Bootstrap. These variables cascade down through every Bootstrap component.
Full Working Example
Here's a complete minimal dark mode page:
<!DOCTYPE html>
<html lang="en" id="htmlRoot">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bootstrap 5 Dark Mode</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
[data-bs-theme="dark"] {
--bs-body-bg: #0a0a0a;
--bs-card-bg: #1a1a2e;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand fw-bold" href="#">My App</a>
<button id="themeToggle" class="btn btn-outline-secondary btn-sm ms-auto">
🌙 Dark Mode
</button>
</div>
</nav>
<div class="container py-5">
<div class="card">
<div class="card-body">
<h5 class="card-title">Dark Mode Works</h5>
<p class="card-text text-muted">This card adapts to light and dark mode automatically.</p>
<button class="btn btn-primary">Primary Button</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
const root = document.getElementById('htmlRoot')
const btn = document.getElementById('themeToggle')
function getTheme() {
return localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
}
function setTheme(theme) {
root.setAttribute('data-bs-theme', theme)
localStorage.setItem('theme', theme)
btn.textContent = theme === 'dark' ? '☀️ Light Mode' : '🌙 Dark Mode'
}
btn.addEventListener('click', () => {
setTheme(getTheme() === 'dark' ? 'light' : 'dark')
})
setTheme(getTheme())
</script>
</body>
</html>
What Doesn't Auto-Switch
One thing to watch: custom background colors set with inline style="" or your own CSS classes won't switch automatically. Only Bootstrap's CSS variables respond to data-bs-theme.
If you've hardcoded style="background:#fff" on elements, those won't go dark. Use Bootstrap's utility classes like bg-body, bg-body-secondary instead — those use CSS variables and respond correctly.
Angular and React
For Angular, store the theme in a service and update the attribute on the document element:
setTheme(theme: 'light' | 'dark') {
document.documentElement.setAttribute('data-bs-theme', theme)
localStorage.setItem('theme', theme)
}
For React with Next.js, use next-themes — it handles Bootstrap's data-bs-theme attribute directly when configured with attribute="data-bs-theme".
Frequently Asked Questions
Need a Full Bootstrap 5 Admin Dashboard?
Get a complete Angular 21 + Bootstrap 5 dashboard with 50+ components — built by the same team behind BootstrapPlanet.
Browse Templates →Use code FIRST30 for 30% off your first purchase.