Bootstrap 5 Tutorial
v5.3.0Bootstrap 5 Tutorial
Dark Mode in Bootstrap 5
Theme Demo
Toggle between light and dark mode to see Bootstrap components adapt.
Why Dark Mode Matters
Dark mode reduces eye strain in low-light conditions, saves battery life on OLED displays, and provides users with a choice that matches their preference or environment.
Light Mode
Bright background with dark text
Dark Mode
Dark background with light text
How Bootstrap 5 Implements Dark Mode
CSS Custom Properties (Variables)
Bootstrap 5 uses CSS custom properties to switch between light and dark themes. The data-bs-theme attribute controls which set of variables is active.
CSS Variable Structure
/* Light theme variables (default) */
:root {
--bs-body-bg: #fff;
--bs-body-color: #212529;
--bs-primary: #0d6efd;
--bs-border-color: #dee2e6;
/* ... more variables */
}
/* Dark theme variables */
[data-bs-theme="dark"] {
--bs-body-bg: #212529;
--bs-body-color: #adb5bd;
--bs-primary: #6ea8fe;
--bs-border-color: #495057;
/* ... more variables */
}
/* Components use variables */
body {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
}
.btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}Theme Switching Mechanism
<!-- HTML structure with theme attribute -->
<html data-bs-theme="light">
<!-- Light theme applied -->
</html>
<!-- Change to dark theme -->
<html data-bs-theme="dark">
<!-- Dark theme applied -->
</html>
<!-- JavaScript to toggle theme -->
// Set theme to dark
document.documentElement.setAttribute('data-bs-theme', 'dark');
// Set theme to light
document.documentElement.setAttribute('data-bs-theme', 'light');
// Toggle between themes
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-bs-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-bs-theme', newTheme);
}
// Detect system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
if (prefersDark.matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark');
}Key Points:
- Bootstrap uses CSS custom properties for theme values
- The
data-bs-themeattribute controls active theme - Components automatically adapt to theme changes
- System preference can be detected with JavaScript
- User preferences should be saved (localStorage, cookies)
Implementing Dark Mode
Basic Implementation
Method 1: Using Bootstrap's Built-in Support
Bootstrap 5.3+ includes built-in dark mode support.
<!-- Include Bootstrap CSS -->
<link href="bootstrap.min.css" rel="stylesheet">
<!-- Set initial theme -->
<html data-bs-theme="light">
<body>
<!-- Your content -->
<!-- Theme toggle button -->
<button id="themeToggle" class="btn btn-primary">
Toggle Theme
</button>
<script>
// Toggle theme on button click
document.getElementById('themeToggle').addEventListener('click', function() {
const currentTheme = document.documentElement.getAttribute('data-bs-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
// Update HTML attribute
document.documentElement.setAttribute('data-bs-theme', newTheme);
// Save preference
localStorage.setItem('theme', newTheme);
});
// Load saved theme on page load
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-bs-theme', savedTheme);
}
</script>
</body>
</html>Method 2: Advanced Implementation with System Detection
Detect system preference and allow user override.
// theme-manager.js
class ThemeManager {
constructor() {
this.theme = null;
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
this.init();
}
init() {
// Check for saved preference
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
// Use saved preference
this.setTheme(savedTheme);
this.theme = savedTheme;
} else {
// Use system preference
this.setTheme(this.mediaQuery.matches ? 'dark' : 'light');
this.theme = null; // null means using system
}
// Listen for system changes (only if using system preference)
this.mediaQuery.addEventListener('change', (e) => {
if (this.theme === null) {
this.setTheme(e.matches ? 'dark' : 'light');
}
});
}
setTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
// Dispatch custom event
window.dispatchEvent(new CustomEvent('themechange', {
detail: { theme }
}));
}
toggleTheme() {
const current = document.documentElement.getAttribute('data-bs-theme');
const newTheme = current === 'dark' ? 'light' : 'dark';
this.setTheme(newTheme);
this.theme = newTheme; // User explicitly chose
localStorage.setItem('theme', newTheme);
}
useSystemPreference() {
const systemTheme = this.mediaQuery.matches ? 'dark' : 'light';
this.setTheme(systemTheme);
this.theme = null; // Using system
localStorage.removeItem('theme');
}
}
// Initialize
const themeManager = new ThemeManager();Component-Specific Dark Mode
Dark Mode Components Showcase
Cards in Dark Mode
Dark Card
This card uses dark mode styling with appropriate contrast ratios.
Forms in Dark Mode
Alerts in Dark Mode
Navigation in Dark Mode
Custom Component Styling for Dark Mode:
/* Custom component with dark mode support */
.custom-component {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
border: 1px solid var(--bs-border-color);
padding: 1rem;
border-radius: var(--bs-border-radius);
}
/* Specific dark mode adjustments */
[data-bs-theme="dark"] .custom-component {
/* Additional dark mode styles */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
/* Using Sass for dark mode styles */
.custom-component {
@include border-radius($border-radius);
background-color: $body-bg;
color: $body-color;
@include dark-mode() {
background-color: $dark-body-bg;
color: $dark-body-color;
border-color: $dark-border-color;
}
}
/* Custom properties for theming */
:root {
--custom-bg: #ffffff;
--custom-color: #212529;
}
[data-bs-theme="dark"] {
--custom-bg: #2d3338;
--custom-color: #e9ecef;
}
.custom-component {
background-color: var(--custom-bg);
color: var(--custom-color);
}Customizing Dark Mode Colors
Custom Dark Theme Variables
Overriding Default Dark Theme
/* custom-dark-theme.css */
/* Override Bootstrap's dark theme variables */
[data-bs-theme="dark"] {
/* Background colors */
--bs-body-bg: #1a1d21;
--bs-body-bg-rgb: 26, 29, 33;
/* Text colors */
--bs-body-color: #e9ecef;
--bs-body-color-rgb: 233, 236, 239;
/* Border colors */
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
/* Primary color - softer for dark mode */
--bs-primary: #6ea8fe;
--bs-primary-rgb: 110, 168, 254;
/* Secondary color */
--bs-secondary: #a0aec0;
--bs-secondary-rgb: 160, 174, 192;
/* Custom colors for dark mode */
--bs-custom-color: #e9ecef;
--bs-custom-bg: #2d3338;
/* Card customization */
--bs-card-bg: #2d3338;
--bs-card-border-color: rgba(255, 255, 255, 0.125);
/* Form customization */
--bs-form-control-bg: #2d3338;
--bs-form-control-color: #e9ecef;
--bs-form-control-border-color: #495057;
/* Link colors */
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
/* Code blocks */
--bs-code-color: #e685b5;
}Creating Multiple Dark Themes
/* multiple-themes.css */
/* Default dark theme */
[data-bs-theme="dark"] {
--bs-body-bg: #212529;
--bs-body-color: #adb5bd;
/* ... other variables */
}
/* Blue dark theme */
[data-bs-theme="dark-blue"] {
--bs-body-bg: #0a1929;
--bs-body-color: #b2bac2;
--bs-primary: #66b2ff;
--bs-border-color: #265d97;
/* ... blue-themed variables */
}
/* Gray dark theme */
[data-bs-theme="dark-gray"] {
--bs-body-bg: #2d3748;
--bs-body-color: #e2e8f0;
--bs-primary: #a0aec0;
--bs-border-color: #4a5568;
/* ... gray-themed variables */
}
/* AMOLED dark theme (true black) */
[data-bs-theme="dark-amoled"] {
--bs-body-bg: #000000;
--bs-body-color: #ffffff;
--bs-primary: #bb86fc;
--bs-border-color: #333333;
/* ... AMOLED variables */
}
/* JavaScript theme switching */
function setTheme(themeName) {
// Remove all theme attributes
document.documentElement.removeAttribute('data-bs-theme');
// Add selected theme
if (themeName !== 'light') {
document.documentElement.setAttribute('data-bs-theme', themeName);
}
// Save preference
localStorage.setItem('theme', themeName);
}
// Theme selector
<select id="themeSelector" class="form-select">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="dark-blue">Dark Blue</option>
<option value="dark-gray">Dark Gray</option>
<option value="dark-amoled">AMOLED Dark</option>
</select>
<script>
document.getElementById('themeSelector').addEventListener('change', function(e) {
setTheme(e.target.value);
});
</script>Images and Media in Dark Mode
Handling Media in Dark Themes
Image Adjustments for Dark Mode
/* Reduce brightness of images in dark mode */
[data-bs-theme="dark"] img {
filter: brightness(0.8) contrast(1.2);
}
/* Specific image adjustments */
[data-bs-theme="dark"] .hero-image {
filter: brightness(0.7);
}
/* Invert light-colored images/logos */
[data-bs-theme="dark"] .logo-light {
filter: invert(1) brightness(2);
}
[data-bs-theme="dark"] .logo-dark {
/* Keep as is */
}
/* SVG adjustments */
[data-bs-theme="dark"] svg.icon {
fill: currentColor;
}
/* Picture element with different sources */
<picture>
<source srcset="image-dark.jpg" media="(prefers-color-scheme: dark)">
<img src="image-light.jpg" alt="Description">
</picture>
/* CSS background images */
.hero {
background-image: url('hero-light.jpg');
}
[data-bs-theme="dark"] .hero {
background-image: url('hero-dark.jpg');
}
/* Using CSS variables for backgrounds */
:root {
--hero-bg: url('hero-light.jpg');
}
[data-bs-theme="dark"] {
--hero-bg: url('hero-dark.jpg');
}
.hero {
background-image: var(--hero-bg);
}Video and Embed Adjustments
/* Video player customizations */
[data-bs-theme="dark"] video {
/* Add dark overlay to video */
position: relative;
}
[data-bs-theme="dark"] video::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
pointer-events: none;
}
/* Iframe embeds (YouTube, etc.) */
[data-bs-theme="dark"] iframe {
/* YouTube dark mode via URL parameter */
/* Add &theme=dark to YouTube embed URL */
}
/* Custom video player controls */
.video-player {
--control-bg: rgba(255, 255, 255, 0.1);
--control-color: white;
}
[data-bs-theme="dark"] .video-player {
--control-bg: rgba(0, 0, 0, 0.7);
--control-color: #e9ecef;
}
/* Chart and data visualization */
[data-bs-theme="dark"] .chart-container {
background-color: var(--bs-body-bg);
}
[data-bs-theme="dark"] .chart-grid line {
stroke: var(--bs-border-color);
}
[data-bs-theme="dark"] .chart-text {
fill: var(--bs-body-color);
}
/* Map containers */
[data-bs-theme="dark"] .map-container {
filter: invert(0.9) hue-rotate(180deg) contrast(1.2);
}
/* For Google Maps */
[data-bs-theme="dark"] .gm-style {
filter: invert(0.9) hue-rotate(180deg) contrast(1.2);
}Media Best Practices for Dark Mode:
- Test images in both themes: Some images may not work well in dark mode
- Consider separate image assets: Different versions for light and dark modes
- Use CSS filters carefully: Can impact performance on large images
- Provide alternatives: Allow users to toggle image adjustments
- Check contrast: Ensure text overlays on images remain readable
- Optimize for performance: Dark mode shouldn't significantly impact load times
Accessibility Considerations
✅ Accessibility Best Practices:
- Maintain contrast ratios: WCAG 2.1 AA requires 4.5:1 for normal text
- Test focus states: Ensure focus indicators are visible in both themes
- Provide theme toggle: Users should control their experience
- Respect reduced motion: Honor
prefers-reduced-motion - Test with screen readers: Ensure content remains accessible
- Consider color blindness: Don't rely solely on color differences
❌ Common Accessibility Issues:
- Insufficient contrast: Light gray text on dark gray background
- Missing focus indicators: Can't navigate with keyboard
- Automatic theme switching: Should respect user preference
- Flashing content: Rapid theme changes can trigger seizures
- Poor image contrast: Text overlays on images become unreadable
- No theme persistence: Theme resets on page reload
Contrast Testing Example:
/* Testing contrast with CSS */
.contrast-good {
/* Passes WCAG AA */
background-color: var(--bs-success);
color: white;
}
.contrast-borderline {
/* May fail for small text */
background-color: var(--bs-warning);
color: var(--bs-dark);
}
.contrast-poor {
/* Fails accessibility */
background-color: var(--bs-secondary);
color: var(--bs-light);
}
/* Using CSS to check contrast */
@media (prefers-contrast: high) {
:root {
--bs-body-color: #000;
--bs-body-bg: #fff;
}
[data-bs-theme="dark"] {
--bs-body-color: #fff;
--bs-body-bg: #000;
}
}
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
.theme-transition {
transition: none !important;
}
}Performance Considerations
Dark Mode Performance
Optimizing Dark Mode CSS
/* Efficient dark mode styles */
/* 1. Use CSS variables (most efficient) */
:root {
--component-bg: white;
--component-color: black;
}
[data-bs-theme="dark"] {
--component-bg: black;
--component-color: white;
}
.component {
background: var(--component-bg);
color: var(--component-color);
}
/* 2. Avoid duplicate styles */
/* ❌ Inefficient */
.component {
background: white;
color: black;
}
[data-bs-theme="dark"] .component {
background: black;
color: white;
}
/* ✅ Efficient */
.component {
background: var(--component-bg);
color: var(--component-color);
}
/* 3. Minimize repaints */
/* Add transition for smooth theme switching */
.theme-transition {
transition: background-color 0.3s ease,
color 0.3s ease,
border-color 0.3s ease;
}
/* But respect reduced motion */
@media (prefers-reduced-motion: reduce) {
.theme-transition {
transition: none;
}
}
/* 4. Lazy load dark mode styles */
<link rel="stylesheet" href="light.css" media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
/* 5. Critical CSS for both themes */
/* Include essential styles for both themes in initial load */JavaScript Performance
// Efficient theme switching
// 1. Debounce rapid theme toggles
let themeSwitchTimeout;
function debouncedSetTheme(theme) {
clearTimeout(themeSwitchTimeout);
themeSwitchTimeout = setTimeout(() => {
document.documentElement.setAttribute('data-bs-theme', theme);
}, 100);
}
// 2. Use requestAnimationFrame for smooth transitions
function smoothThemeChange(theme) {
requestAnimationFrame(() => {
document.documentElement.setAttribute('data-bs-theme', theme);
});
}
// 3. Minimize DOM operations
// Cache theme state
let currentTheme = document.documentElement.getAttribute('data-bs-theme');
function toggleTheme() {
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
currentTheme = newTheme;
document.documentElement.setAttribute('data-bs-theme', newTheme);
}
// 4. Use passive event listeners
document.addEventListener('click', handleThemeToggle, { passive: true });
// 5. Load theme preferences early
// Place script in head with defer
<script defer>
// Check theme immediately
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-bs-theme', savedTheme);
}
</script>
// 6. Use Intersection Observer for lazy theming
// Only apply dark mode styles to visible elements
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && isDarkMode) {
entry.target.classList.add('dark-mode-active');
}
});
});Testing Dark Mode
Testing Checklist
- Visual inspection: Check all components in both themes
- Contrast testing: Use tools like axe DevTools or Lighthouse
- Browser testing: Test in Chrome, Firefox, Safari, Edge
- Mobile testing: Check on iOS and Android devices
- Performance testing: Monitor load times and repaints
- User preference testing: Test system preference detection
- JavaScript testing: Test theme switching functionality
- Accessibility testing: Keyboard navigation, screen readers
Testing Tools
- Chrome DevTools: Toggle prefers-color-scheme emulation
- Lighthouse: Accessibility and best practices audits
- axe DevTools: Automated accessibility testing
- Color Contrast Checkers: WebAIM, Contrast Ratio
- BrowserStack/CrossBrowserTesting: Cross-browser testing
- Performance Profilers: Chrome Performance tab
- Manual Testing: Actual device testing
Quick Test in Chrome DevTools:
- Open DevTools (F12)
- Go to Rendering tab (under More Tools)
- Emulate CSS media feature: prefers-color-scheme
- Toggle between light and dark
- Check for styling issues
Dark Mode Implementation Summary
- Use Bootstrap's built-in support: Leverage
data-bs-themeand CSS variables - Respect user preferences: Detect system theme and save user choices
- Maintain accessibility: Ensure proper contrast and keyboard navigation
- Customize carefully: Override theme variables for brand consistency
- Optimize performance: Use efficient CSS and debounced JavaScript
- Test thoroughly: Check all components in both themes across devices
- Provide user control: Always include a theme toggle option
- Consider edge cases: Images, embeds, and third-party content
Quick Reference
data-bs-theme=&qout;dark&qout;- Enable dark modeprefers-color-scheme: dark- CSS media query--bs-body-bg- Body background variablematchMedia('(prefers-color-scheme: dark)')- JS detectionlocalStorage.setItem('theme', 'dark')- Save preferencefilter: brightness(0.8)- Darken images