Bootstrap 5.3's native dark mode support makes implementing dark theme in Angular clean and simple. No custom CSS switching, no toggling classes on hundreds of elements — just set one attribute on the html element.
Here's a complete service-based implementation using Angular 21 signals.
The Theme Service
// theme.service.ts import { Injectable, signal, effect } from '@angular/core' type Theme = 'light' | 'dark' @Injectable({ providedIn: 'root' }) export class ThemeService { // Signal holds the current theme theme = signal<Theme>(this.getInitialTheme()) constructor() { // Effect runs whenever theme signal changes effect(() => { this.applyTheme(this.theme()) }) } toggle() { this.theme.update(current => current === 'light' ? 'dark' : 'light') } setTheme(theme: Theme) { this.theme.set(theme) } isDark() { return this.theme() === 'dark' } private getInitialTheme(): Theme { // 1. Check localStorage const saved = localStorage.getItem('theme') as Theme | null if (saved === 'light' || saved === 'dark') return saved // 2. Check system preference if (window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark' // 3. Default to light return 'light' } private applyTheme(theme: Theme) { document.documentElement.setAttribute('data-bs-theme', theme) localStorage.setItem('theme', theme) } }
The effect() function runs automatically whenever theme signal changes. No subscriptions to manage, no ngOnDestroy cleanup needed.
The Toggle Button Component
// theme-toggle.component.ts import { Component, inject } from '@angular/core' import { CommonModule } from '@angular/common' import { ThemeService } from './theme.service' @Component({ selector: 'app-theme-toggle', standalone: true, imports: [CommonModule], template: ` <button class="btn btn-sm btn-outline-secondary d-flex align-items-center gap-2" (click)="themeService.toggle()" [attr.aria-label]="themeService.isDark() ? 'Switch to light mode' : 'Switch to dark mode'"> {{ themeService.isDark() ? '☀️' : '🌙' }} <span class="d-none d-sm-inline"> {{ themeService.isDark() ? 'Light' : 'Dark' }} </span> </button> ` }) export class ThemeToggleComponent { themeService = inject(ThemeService) }
Add to your navbar:
<app-theme-toggle />
That's all. The service handles everything — applying the theme, persisting it, reading system preference on first load.
Using Theme State in Components
Read the current theme anywhere using the signal:
// any.component.ts import { Component, inject, computed } from '@angular/core' import { ThemeService } from './theme.service' @Component({ standalone: true, template: ` <div [class]="containerClass()"> Content that changes with theme </div> ` }) export class SomeComponent { private themeService = inject(ThemeService) containerClass = computed(() => this.themeService.isDark() ? 'bg-dark text-white p-4 rounded' : 'bg-light text-dark p-4 rounded' ) }
computed() creates a derived signal that automatically updates when theme changes.
System Preference Listener
To respond to system dark mode changes in real time (user changes OS settings while the app is open):
// Add to ThemeService constructor constructor() { effect(() => { this.applyTheme(this.theme()) }) // Listen for system preference changes // Only update if user hasn't manually set a preference window.matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', (e) => { const hasManualPreference = localStorage.getItem('theme') if (!hasManualPreference) { this.theme.set(e.matches ? 'dark' : 'light') } }) }
Custom Dark Mode Colors
Bootstrap's dark mode uses CSS variables you can override in your styles.scss:
[data-bs-theme="dark"] { --bs-body-bg: #0a0f1e; // Deep dark background --bs-body-color: #e2e8f0; // Light text --bs-card-bg: #111827; // Dark card background --bs-border-color: #1e293b; // Subtle borders --bs-secondary-bg: #0f172a; // Secondary background --bs-tertiary-bg: #0d0d0d; // Darkest background // Custom brand colors stay consistent --bs-primary: #fd4766; --bs-primary-rgb: 253, 71, 102; }
Applying Dark Theme to Specific Components Only
If you want a dark sidebar on a light app:
// sidebar.component.ts import { Component, ElementRef, OnInit, inject } from '@angular/core' import { ThemeService } from './theme.service' @Component({ selector: 'app-sidebar', standalone: true, // Note: NO data-bs-theme here — we set it programmatically template: `<nav class="sidebar"><!-- content --></nav>`, host: { 'attr.data-bs-theme': 'dark' } // Always dark sidebar }) export class SidebarComponent {}
Or for a sidebar that's always dark regardless of global theme, just set it in the template:
<aside data-bs-theme="dark" style="background:#0d0d0d;"> <!-- This sidebar is always dark --> </aside>
Clean, simple, and Bootstrap handles all the component styling inside automatically.
Frequently Asked Questions
Related
Need a Full Angular + Bootstrap Admin Dashboard?
Marvel Angular Dashboard — Angular 21 + Bootstrap 5 with 50+ components and dark mode.
Browse Templates →Use code FIRST30 for 30% off.