Bootstrap 5.3 Dark Mode: The Right Way vs The Wrong WaysteemCreated with Sketch.

in #bootstrap14 days ago

Dark mode is expected in 2026. Users switch it on and don't think about it again. When a dashboard doesn't support dark mode — they notice. When dark mode is implemented badly — flickering on load, inconsistent colors, some components going dark while others stay light — they notice that too.

Bootstrap 5.3 ships native color mode support. Most developers don't use it correctly. Here's the right way and the wrong way.
The Wrong Way — Class Toggle on Body
The approach I see most often in Bootstrap templates:

// Wrong approach
document.body.classList.toggle('dark-mode')
/* Wrong — duplicate CSS for every component */
.dark-mode .card { background: #1e2130; color: #e2e8f0; }
.dark-mode .sidebar { background: #161b2e; }
.dark-mode .navbar { background: #1a1f2e; border-color: #2d3748; }
.dark-mode .table { color: #e2e8f0; border-color: #2d3748; }
.dark-mode .modal-content { background: #1e2130; }
/* 200 more lines of duplicate CSS */

This works but it's maintenance debt. Every new component needs duplicate dark mode CSS. Every Bootstrap update might conflict with your overrides. Rebranding means updating colors in two places — light mode and dark mode — for every component.

The Right Way — Bootstrap 5.3 Native Color Modes

Bootstrap 5.3 introduced data-bs-theme attribute support. Every Bootstrap component responds to it automatically.

// Toggle dark mode — one line
document.documentElement.setAttribute(
  'data-bs-theme',
  currentTheme === 'dark' ? 'light' : 'dark'
)
localStorage.setItem('theme', document.documentElement.getAttribute('data-bs-theme'))
// On page load — restore saved preference
const savedTheme = localStorage.getItem('theme') ||
  (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
document.documentElement.setAttribute('data-bs-theme', savedTheme)

Every Bootstrap component — cards, modals, dropdowns, tables, forms, badges, alerts — switches automatically. No duplicate CSS needed for Bootstrap components.

Handling Custom Components

Your custom components — stat cards, sidebar, custom charts — need explicit dark mode handling using CSS custom properties:

// Define custom properties for both themes
:root {
  --app-sidebar-bg: #f8f9fa;
  --app-sidebar-active-bg: rgba(253, 71, 102, 0.08);
  --app-sidebar-active-color: #fd4766;
  --app-stat-card-bg: #ffffff;
  --app-stat-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  --app-chart-grid-color: rgba(0, 0, 0, 0.06);
}

[data-bs-theme="dark"] {
  --app-sidebar-bg: #161b2e;
  --app-sidebar-active-bg: rgba(253, 71, 102, 0.12);
  --app-sidebar-active-color: #ff6b84;
  --app-stat-card-bg: #1e2130;
  --app-stat-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  --app-chart-grid-color: rgba(255, 255, 255, 0.06);
}

// Components use variables — switch automatically
.sidebar {
  background: var(--app-sidebar-bg);

  .nav-item.active {
    background: var(--app-sidebar-active-bg);
    color: var(--app-sidebar-active-color);
  }
}

.stat-card {
  background: var(--app-stat-card-bg);
  box-shadow: var(--app-stat-card-shadow);
}

One attribute change on the root element. Bootstrap components switch automatically. Custom components using CSS custom properties switch automatically. No JavaScript toggling classes on individual elements. No duplicate CSS.

Respecting System Preference

// Detect system preference on first visit
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')

// Set initial theme from system preference if no saved preference
if (!localStorage.getItem('theme')) {
  document.documentElement.setAttribute(
    'data-bs-theme',
    prefersDark.matches ? 'dark' : 'light'
  )
}

// Update if system preference changes while page is open
prefersDark.addEventListener('change', (e) => {
  if (!localStorage.getItem('theme')) {
    document.documentElement.setAttribute(
      'data-bs-theme',
      e.matches ? 'dark' : 'light'
    )
  }
})

This is the complete dark mode implementation — system preference detection, manual toggle, localStorage persistence. About 25 lines of JavaScript. No library needed. Works with Bootstrap 5.3's native color mode system.

Bootstrap 5.3 admin dashboard templates with proper dark mode implementation — browse lettstartdesign.com/category/bootstrap-templates. Use FIRST30 for 30% off.