Dark Mode
1. Course Introduction
(1) Prerequisites
- Bootstrap Sass Customization Flow (Lesson 20)
- Basics of CSS Custom Properties (CSS Variables)
- Basics of JavaScript DOM Manipulation
(2) 🎯 What You'll Learn
- Enable dark mode using the
data-bs-themeattribute - Implement a theme toggle button + localStorage persistence
- Automatically follow the system's
prefers-color-schemepreference - Customize dark mode CSS variables
- Master adaptation techniques for images and components in dark mode
(3) Pain Point
Charlie's admin dashboard needs a "Day/Night Mode" toggle button. The traditional implementation requires writing two sets of styles for each component, which is extremely costly to maintain and prone to style omissions during switching.
(4) Solution
Bootstrap 5.3 introduces native dark mode support. Using the data-bs-theme="dark" attribute, the entire page or individual components can switch between light/dark themes, with all component styles automatically adapting. Global theme switching is achieved with just a single attribute change.
Understanding: Dark Mode = a "filter" for the theme. In Bootstrap 5.3, all components have two sets of CSS variables for
lightanddark. Thedata-bs-themeattribute determines which set is used.
(5) Benefit
Charlie only needs to add a toggle button. A single data-bs-theme attribute change enables global theme switching, and all component styles automatically adapt to dark mode without needing to manually write any dark override styles.
2. Basic Dark Mode
▶ Example: Basic Dark Mode Demonstration
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark Mode Demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-4">
<!-- Light mode section -->
<h5>Light Mode (default)</h5>
<div class="card p-3 mb-3">
<p>This card uses the default light theme.</p>
<button class="btn btn-primary">Button</button>
</div>
<!-- Dark mode section -->
<div data-bs-theme="dark">
<h5>Dark Mode</h5>
<div class="card p-3 mb-3">
<p>This card automatically adapts to dark theme.</p>
<button class="btn btn-primary">Button</button>
<div class="alert alert-warning mt-2">Alert also adapts.</div>
</div>
</div>
</div>
</body>
</html>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
3. Three Scopes
| Scope | Syntax | Description |
|---|---|---|
| Global | <html data-bs-theme="dark"> |
The entire page becomes dark mode |
| Sectional | <div data-bs-theme="dark"> |
Components within the specified container become dark |
| Component | <div class="card" data-bs-theme="dark"> |
A single card becomes dark |
<!-- Global dark mode -->
<html data-bs-theme="dark">
<!-- all components use dark theme -->
</html>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
4. Theme Toggle Button
<button id="themeToggle" class="btn btn-outline-primary">
<i class="bi bi-moon"></i> Dark Mode
</button>
<script>
const toggle = document.getElementById('themeToggle')
toggle.addEventListener('click', () => {
const html = document.documentElement
const current = html.getAttribute('data-bs-theme')
const next = current === 'dark' ? 'light' : 'dark'
html.setAttribute('data-bs-theme', next)
toggle.innerHTML = next === 'dark'
? '<i class="bi bi-sun"></i> Light Mode'
: '<i class="bi bi-moon"></i> Dark Mode'
})
</script>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
(1) Remembering User Preference
<script>
// Check saved preference on load
const saved = localStorage.getItem('theme')
if (saved) {
document.documentElement.setAttribute('data-bs-theme', saved)
}
document.getElementById('themeToggle').addEventListener('click', () => {
const html = document.documentElement
const current = html.getAttribute('data-bs-theme') || 'light'
const next = current === 'dark' ? 'light' : 'dark'
html.setAttribute('data-bs-theme', next)
localStorage.setItem('theme', next)
})
</script>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
5. Automatically Following System Preference
<script>
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')
const saved = localStorage.getItem('theme')
if (saved) {
document.documentElement.setAttribute('data-bs-theme', saved)
} else if (prefersDark.matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
}
// Listen for system theme change
prefersDark.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
document.documentElement.setAttribute('data-bs-theme', e.matches ? 'dark' : 'light')
}
})
</script>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
6. CSS Variables for Dark Mode
Bootstrap 5.3 uses CSS custom properties for theme switching. You can directly override dark mode variables in your CSS:
/* Custom dark mode colors */
[data-bs-theme="dark"] {
--bs-body-bg: #1a1a2e;
--bs-body-color: #e0e0e0;
--bs-primary: #bb86fc;
--bs-primary-rgb: 187, 134, 252;
--bs-card-bg: #16213e;
--bs-border-color: #2a2a4a;
}
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
(1) Sass Approach
// scss/custom.scss
$enable-dark-mode: true;
// Override dark mode colors
$dark-bg: #1a1a2e;
$dark-card-bg: #16213e;
$primary-dark: #bb86fc;
// Import Bootstrap
@import "../node_modules/bootstrap/scss/bootstrap";
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
7. Dark Mode Image Adaptation
<img src="logo-light.png" class="d-block d-dark-none" alt="Logo light">
<img src="logo-dark.png" class="d-none d-dark-block" alt="Logo dark">
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default. Bootstrap 5.3 provides display utility classes specific to dark mode, such as
d-dark-blockandd-dark-none.
8. Comprehensive Examples
▶ Example: Complete Dark Mode Toggle Page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark Mode Toggle Demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand" href="#"><i class="bi bi-moon-stars me-2"></i>Theme Demo</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="nav">
<ul class="navbar-nav ms-auto align-items-center">
<li class="nav-item"><a class="nav-link active" href="#">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#">Features</a></li>
<li class="nav-item"><a class="nav-link" href="#">Pricing</a></li>
<li class="nav-item ms-2">
<button id="themeToggle" class="btn btn-outline-primary btn-sm rounded-pill">
<i class="bi bi-moon"></i> <span id="themeLabel">Dark</span>
</button>
</li>
</ul>
</div>
</div>
</nav>
<div class="container py-5">
<h1>Dark Mode Demo</h1>
<p class="lead">Click the toggle button to switch between light and dark themes.</p>
<div class="row g-4 mb-5">
<div class="col-md-4">
<div class="card p-4">
<h5>Card Component</h5>
<p>Automatically adapts to theme.</p>
<button class="btn btn-primary">Primary</button>
</div>
</div>
<div class="col-md-4">
<div class="card p-4">
<h5>Alert Example</h5>
<div class="alert alert-success mt-2">Success alert</div>
<div class="alert alert-danger">Danger alert</div>
<div class="alert alert-info">Info alert</div>
</div>
</div>
<div class="col-md-4">
<div class="card p-4">
<h5>Form Elements</h5>
<input type="text" class="form-control mb-2" placeholder="Input field">
<select class="form-select mb-2"><option>Option 1</option></select>
<div class="form-check"><input class="form-check-input" type="checkbox" id="ck"><label class="form-check-label" for="ck">Check me</label></div>
</div>
</div>
</div>
<div class="card p-4">
<h5>Table</h5>
<table class="table">
<thead><tr><th>Name</th><th>Role</th><th>Status</th></tr></thead>
<tbody>
<tr><td>Alice</td><td>Admin</td><td><span class="badge bg-success">Active</span></td></tr>
<tr><td>Bob</td><td>Editor</td><td><span class="badge bg-warning">Pending</span></td></tr>
<tr><td>Charlie</td><td>Viewer</td><td><span class="badge bg-secondary">Inactive</span></td></tr>
</tbody>
</table>
</div>
</div>
<script>
const toggle = document.getElementById('themeToggle');
const label = document.getElementById('themeLabel');
const icon = toggle.querySelector('i');
function setTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('theme', theme);
const isDark = theme === 'dark';
label.textContent = isDark ? 'Dark' : 'Light';
icon.className = isDark ? 'bi bi-moon' : 'bi bi-sun';
}
const saved = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
setTheme(saved || (prefersDark.matches ? 'dark' : 'light'));
toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-bs-theme');
setTheme(current === 'dark' ? 'light' : 'dark');
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
▶ Example: Sass Dark Mode + CSS Variables
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark Mode + Sass</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
--brand-primary: #6f42c1;
--brand-accent: #e83e8c;
}
[data-bs-theme="dark"] {
--brand-primary: #bb86fc;
--brand-accent: #ff4081;
--custom-bg: #1a1a2e;
--custom-card-bg: #16213e;
}
.custom-section {
background: var(--custom-bg, #f8f9fa);
padding: 2rem;
border-radius: 0.5rem;
}
.custom-card-css {
background: var(--custom-card-bg, #ffffff);
border: 1px solid var(--bs-border-color, #dee2e6);
border-radius: 0.5rem;
padding: 1.5rem;
}
.brand-icon { color: var(--brand-primary); font-size: 2rem; }
</style>
</head>
<body>
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5>Sass + CSS Variables</h5>
<button class="btn btn-outline-primary btn-sm" onclick="document.documentElement.setAttribute('data-bs-theme', document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark')">
<i class="bi bi-arrow-repeat"></i> Toggle Theme
</button>
</div>
<div class="custom-section">
<h5>Custom Section</h5>
<p>Background uses <code>--custom-bg</code> CSS variable, changes with theme.</p>
</div>
<div class="row g-3 mt-3">
<div class="col-md-4">
<div class="custom-card-css">
<i class="bi bi-palette brand-icon d-block mb-2"></i>
<h6>Brand Color</h6>
<p class="text-body-secondary small">Primary: <code>--brand-primary</code> changes per theme.</p>
</div>
</div>
<div class="col-md-4">
<div class="card p-4">
<h5>Bootstrap Card</h5>
<p class="text-body-secondary">Native dark mode support.</p>
<button class="btn btn-primary">Button</button>
</div>
</div>
<div class="col-md-4">
<div class="custom-card-css">
<i class="bi bi-heart brand-icon d-block mb-2"></i>
<h6>Accent Color</h6>
<p class="text-body-secondary small">Accent: <code>--brand-accent</code> for highlights.</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
▶ Example: Dark Mode Image Switching
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark Mode Image Switch</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5>Image Switch for Dark Mode</h5>
<button class="btn btn-outline-primary btn-sm rounded-pill" onclick="toggleTheme()">
<i class="bi bi-moon"></i> Toggle
</button>
</div>
<div class="row g-4">
<div class="col-md-6">
<div class="card p-4">
<h5>Method 1: d-dark-block / d-dark-none</h5>
<p class="text-body-secondary">Shows different image per theme.</p>
<div class="text-center p-4 bg-light rounded-3">
<i class="bi bi-sun-fill fs-1 text-warning d-dark-none"></i>
<i class="bi bi-moon-fill fs-1 text-primary d-none d-dark-block"></i>
<p class="mt-2 mb-0"><span class="d-dark-none">☀️ Light Mode Image</span><span class="d-none d-dark-block">🌙 Dark Mode Image</span></p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card p-4">
<h5>Method 2: CSS Filter</h5>
<p class="text-body-secondary">Invert colors for dark mode.</p>
<div class="text-center p-4 bg-light rounded-3">
<img src="https://via.placeholder.com/200x100/0d6efd/fff?text=Logo" class="img-fluid dark-invert" alt="Logo" style="max-width:200px;">
</div>
<style>
[data-bs-theme="dark"] .dark-invert {
filter: invert(0.85) hue-rotate(180deg);
}
</style>
</div>
</div>
</div>
<div class="card p-4 mt-3">
<h5>Method 3: picture element</h5>
<div class="text-center">
<picture>
<source srcset="https://via.placeholder.com/400x100/1a1a2e/bb86fc?text=Dark+Logo" media="(prefers-color-scheme: dark)" id="darkSource">
<img src="https://via.placeholder.com/400x100/0d6efd/fff?text=Light+Logo" class="img-fluid rounded-3" alt="Responsive Logo" style="max-width:400px;">
</picture>
</div>
</div>
</div>
<script>
function toggleTheme() {
const html = document.documentElement;
const next = html.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark';
html.setAttribute('data-bs-theme', next);
}
</script>
</body>
</html>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
▶ Example: Partial Dark Mode Page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Partial Dark Mode</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-4">
<h5>Sectional Dark Mode Demo</h5>
<p class="text-body-secondary">Different sections of the same page use different themes.</p>
<!-- Light card -->
<div class="row g-4">
<div class="col-md-6">
<div data-bs-theme="light">
<div class="card p-4 border-0 shadow-sm">
<h5>☀️ Light Section</h5>
<p>This section stays light regardless of global theme.</p>
<div class="d-flex gap-2">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-outline-secondary">Secondary</button>
</div>
<div class="alert alert-info mt-3">Light mode alert</div>
<table class="table table-striped mt-3">
<thead><tr><th>Item</th><th>Value</th></tr></thead>
<tbody><tr><td>Alpha</td><td>100</td></tr><tr><td>Beta</td><td>200</td></tr></tbody>
</table>
</div>
</div>
</div>
<!-- Dark card -->
<div class="col-md-6">
<div data-bs-theme="dark">
<div class="card p-4 border-0 shadow-sm">
<h5>🌙 Dark Section</h5>
<p>This section stays dark — useful for dashboards.</p>
<div class="d-flex gap-2">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-outline-secondary">Secondary</button>
</div>
<div class="alert alert-info mt-3">Dark mode alert</div>
<table class="table table-striped mt-3">
<thead><tr><th>Item</th><th>Value</th></tr></thead>
<tbody><tr><td>Alpha</td><td>100</td></tr><tr><td>Beta</td><td>200</td></tr></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Mixed card: components with individual themes -->
<h5 class="mt-5">Component-Level Themes</h5>
<div class="d-flex gap-3 flex-wrap">
<div class="card p-3" data-bs-theme="light" style="width:200px;">
<h6>Light Card</h6>
<button class="btn btn-primary btn-sm">Button</button>
</div>
<div class="card p-3" data-bs-theme="dark" style="width:200px;">
<h6>Dark Card</h6>
<button class="btn btn-primary btn-sm">Button</button>
</div>
<div class="card p-3" style="width:200px;">
<h6>📕 Follows Global</h6>
<button class="btn btn-primary btn-sm">Button</button>
</div>
</div>
</div>
</body>
</html>
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
(1) Dark Mode System Architecture
graph TD
subgraph "Theme Sources"
A1[User Clicks Toggle Button]
A2[System Preference matchMedia]
A3[localStorage Storage]
end
subgraph "Theme Application"
B[data-bs-theme attribute]
C[CSS Variable Switch]
D[Component Style Redraw]
end
subgraph "Theme Detection"
E[window.matchMedia<br/>prefers-color-scheme]
F[getAttribute<br/>data-bs-theme]
G[localStorage.getItem]
end
A1 --> B
A2 --> B
A3 --> B
B --> C
C --> D
E --> A2
F --> A1
G --> A3
Output: The effect of components styled by Bootstrap 5.3 (such as buttons, cards, carousels, collapses, etc.). The page uses Bootstrap's default theme (light) and responsive grid by default.
(2) Dark Mode CSS Variables Table
| Variable Name | Light Default | Dark Default | Purpose |
|---|---|---|---|
--bs-body-bg |
#fff |
#212529 |
Page background color |
--bs-body-color |
#212529 |
#dee2e6 |
Body text color |
--bs-card-bg |
#fff |
#2b3035 |
Card background color |
--bs-border-color |
#dee2e6 |
#495057 |
Border color |
--bs-primary |
#0d6efd |
#6ea8fe |
Theme primary color |
--bs-secondary |
#6c757d |
#adb5bd |
Secondary color |
--bs-navbar-color |
rgba(0,0,0,0.55) |
rgba(255,255,255,0.55) |
Navbar text color |
(3) Theme Detection Methods Comparison Table
| Method | Implementation | Advantages | Disadvantages | Recommended Scenario |
|---|---|---|---|---|
| Manual User Toggle | JS + localStorage |
User control, preference persistence | Requires additional UI element | All applications |
| System Auto-Follow | matchMedia('prefers-color-scheme') |
Seamless adaptation, modern browser support | Cannot switch offline | News sites, blogs |
| Time-Based Auto-Switch | JS checking current time | Automatic dark at night | May not match user preference | Reading applications |
| URL Parameter + Cookie | Server-side reading | No flash of content on first load | Complex implementation | SEO-critical pages |
(4) CSS color-scheme Property Table
| Property Value | Effect | Description |
|---|---|---|
color-scheme: light |
Browser uses light scrollbar and form controls | Default value |
color-scheme: dark |
Browser uses dark scrollbar and form controls | For use with dark mode |
color-scheme: light dark |
Browser automatically selects based on system preference | Recommended for global setting |
color-scheme: normal |
No scheme set | Browser decides |
<meta name="color-scheme" content="light dark"> |
Declared in HTML head | Quick global config without CSS |
❓ Frequently Asked Questions
[data-bs-theme="dark"] selector, or reference Bootstrap's theme variables like var(--bs-body-bg).data-bs-theme="dark" can be applied to any HTML element, affecting only its internal content. Testing shows sectional dark + global light can be mixed.localStorage is a synchronous operation. The only note: when using matchMedia to listen for system theme changes, the callback function should avoid complex calculations and remain lightweight.[data-bs-theme="dark"]; (2) Use their built-in theme API (e.g., Flatpickr's theme: "dark"); (3) Manually write dark override styles using Bootstrap's dark CSS variables as reference.📖 Summary
data-bs-theme="dark"enables dark mode, applied tohtml, a container, or a component- Theme switching: JS modifies the
data-bs-themeattribute +localStoragesaves preference - System preference:
matchMedia('prefers-color-scheme: dark')automatically follows - CSS variable override:
[data-bs-theme="dark"] { --bs-body-bg: ... } - Images:
d-dark-block/d-dark-nonecontrol visibility under different themes
📝 Assignments
- ⭐ Add a dark mode toggle button to the enterprise site from Lesson 19. The button should switch the global theme, with the preference stored in
localStorage. - ⭐⭐ Customize a set of brand dark variables: background
#0d1117(GitHub style), card background#161b22, primary color#58a6ff. - ⭐⭐⭐ Display both light and dark cards on the same page (set
data-bs-themerespectively), and compare the differences in text color, background, border, and buttons.
Previous Lesson: Sass Customization · Next Lesson: Performance Optimization & Build Deployment



