Theming

Dark Mode

Dark mode that feels native — light, dark, or follow the system.

CSS Variables
CSS variables switch themes instantly.
System Aware
Tracks your OS setting and updates in real time.
Persistent
Remembers your choice across sessions.

How it Works

A quick look under the hood.

Dark mode is implemented using a .dark class on the <html> element. When this class is present, Tailwind's dark mode variants become active, applying the dark theme variables.

No Flash of Unstyled Content

The theme is set before first paint by reading localStorage in a head script.

1

Tailwind Configuration

Enable dark mode variants.

Add the dark mode variant to your styles.scss:

styles.scss
@import "tailwindcss";

/* Custom dark mode variant - activates styles inside .dark class */
@custom-variant dark (&:is(.dark *));
2

Theme Service

Manage theme state with signals.

A small service that applies the theme and stays in sync with system preferences:

theme.service.ts
import { computed, effect, Injectable, signal } from '@angular/core';

export type Theme = 'light' | 'dark' | 'system';

@Injectable({ providedIn: 'root' })
export class ThemeService {
  readonly theme = signal<Theme>(this.getStoredTheme());

  readonly isDark = computed(() => {
    const theme = this.theme();
    if (theme === 'system') {
      return this.systemPrefersDark();
    }
    return theme === 'dark';
  });

  private readonly systemPrefersDark = signal(
    this.getSystemPreference()
  );

  constructor() {
    // Apply theme class reactively
    effect(() => {
      document.documentElement.classList.toggle('dark', this.isDark());
    });

    // Listen for system preference changes
    window
      .matchMedia('(prefers-color-scheme: dark)')
      .addEventListener('change', (e) => {
        this.systemPrefersDark.set(e.matches);
      });
  }

  setTheme(theme: Theme): void {
    this.theme.set(theme);
    localStorage.setItem('theme', theme);
  }

  private getStoredTheme(): Theme {
    return (localStorage.getItem('theme') as Theme) || 'system';
  }

  private getSystemPreference(): boolean {
    return window.matchMedia('(prefers-color-scheme: dark)').matches;
  }
}
3

Theme Toggle Component

Let users choose a theme.

theme-toggle.component.ts
import { Component, inject } from '@angular/core';
import { ThemeService, Theme } from './theme.service';
import { Button } from '@/ui/button';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger
} from '@/ui/dropdown-menu';
import { Sun, Moon } from 'lucide-angular';

@Component({
  selector: 'ThemeToggle',
  imports: [
    Button,
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuItem,
    DropdownMenuTrigger
  ],
  template: `
    <DropdownMenu>
      <DropdownMenuTrigger>
        <Button variant="ghost" size="icon">
          @if (themeService.isDark()) {
            <lucide-icon [img]="Moon" class="h-5 w-5" />
          } @else {
            <lucide-icon [img]="Sun" class="h-5 w-5" />
          }
          <span class="sr-only">Toggle theme</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem (click)="setTheme('light')">
          Light
        </DropdownMenuItem>
        <DropdownMenuItem (click)="setTheme('dark')">
          Dark
        </DropdownMenuItem>
        <DropdownMenuItem (click)="setTheme('system')">
          System
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  `,
})
export class ThemeToggle {
  protected readonly themeService = inject(ThemeService);

  protected setTheme(theme: Theme): void {
    this.themeService.setTheme(theme);
  }
}
4

Usage

Drop it into your app.

header.component.html
<!-- In your header component template -->
<header class="flex items-center justify-between border-b px-4 py-3">
  <a routerLink="/" class="font-semibold">
    My App
  </a>

  <nav class="flex items-center gap-4">
    <a routerLink="/docs">Docs</a>
    <a routerLink="/components">Components</a>
  </nav>

  <ThemeToggle />
</header>

Live Preview

Light
Dark
System

Current theme: system

System Preference Detection

Automatically follows your OS setting.

When the theme is set to "system", the service listens to the prefers-color-scheme media query and updates when the user changes their system preference.

Light Mode

Used when the system is set to light

Dark Mode

Used when the system is set to dark