Modern Theme Management for Angular - A lightweight, feature-rich theme library with automatic dark mode detection, SSR support, and zero configuration required.
- π¨ Automatic Theme Detection - Supports light, dark, and system themes with OS preference detection
- β‘ Angular 20 Signals - Built with modern Angular signals for optimal performance and reactivity
- π₯οΈ SSR Compatible - Works perfectly with Angular SSR and server-side rendering
- π― Zero Configuration - Works out of the box with sensible defaults
- π§ Flexible Strategy - Choose between class-based or attribute-based theming
- π¦ Tiny Bundle - Lightweight with no unnecessary dependencies
- π‘οΈ Production Ready - Comprehensive error handling and memory leak prevention
- βΏ Accessibility Friendly - Respects user preferences and system settings
- π Performance Optimized - Efficient DOM updates and minimal re-renders
- π Type Safe - Full TypeScript support with strict type checking
- π§ͺ Tested - Comprehensive test coverage for reliability
- π Well Documented - Extensive documentation with real-world examples
- βοΈ Modern Architecture - Uses Angular's app initializer for clean, testable initialization
npm install @angularui/theme
Add the theme provider to your app.config.ts
:
import { ApplicationConfig } from '@angular/core';
import { provideUiTheme } from '@angularui/theme';
export const appConfig: ApplicationConfig = {
providers: [
provideUiTheme()
]
};
import { Component, inject } from '@angular/core';
import { ThemeService } from '@angularui/theme';
@Component({
selector: 'app-header',
template: `
<header>
<h1>My App</h1>
<button (click)="toggleTheme()">Toggle Theme</button>
<p>Current theme: {{ themeService.theme() }}</p>
<p>Resolved theme: {{ themeService.resolvedTheme() }}</p>
</header>
`
})
export class HeaderComponent {
private themeService = inject(ThemeService);
toggleTheme() {
this.themeService.toggle();
}
}
/* Default styles (light theme) */
:root {
--bg-color: #ffffff;
--text-color: #000000;
--primary-color: #3b82f6;
}
/* Dark theme styles */
.dark {
--bg-color: #1f2937;
--text-color: #f9fafb;
--primary-color: #60a5fa;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
- Native Angular Integration - Built specifically for Angular with signals, dependency injection, and modern patterns
- TypeScript First - Full type safety with comprehensive TypeScript support
- Angular 20+ Ready - Uses latest Angular features like signals and standalone components
- Modern DI Pattern - Uses Angular's inject() function for better performance and tree-shaking
- Future-Proof - Built with Angular's latest patterns and best practices
- Enterprise Ready - Designed for large-scale applications with proper error handling
- Clean Architecture - Uses app initializer for testable, flexible initialization
- Consistent Theming - Standardized approach across Angular applications
- Developer Experience - Excellent IDE support with full autocomplete
- Performance - Leverages Angular's signal system for optimal reactivity
- Maintainability - Clean, well-documented API following Angular conventions
- Community - Contributes to Angular's rich ecosystem of tools
- Reduced Bundle Size - Tree-shakeable and optimized for production
- Better Testing - App initializer pattern enables easier unit testing
@angularui/theme uses Angular's provideAppInitializer()
for clean, testable initialization:
// Traditional approach (other libraries)
constructor() {
this.initialize(); // Side effects in constructor
}
// @angularui/theme approach
provideAppInitializer(() => {
const themeService = inject(ThemeService);
themeService.initialize(); // Clean, controlled initialization
return Promise.resolve();
})
- π Testable - Can test service without auto-initialization
- β‘ Performant - No constructor side effects
- π― Controlled - Can conditionally initialize based on app state
- π§Ή Clean - Separation of concerns
- π§ Flexible - Manual initialization when needed
- π Modern - Follows Angular 20+ best practices
interface ThemeConfig {
defaultTheme?: 'light' | 'dark' | 'system'; // Default: 'system'
storageKey?: string; // Default: 'theme'
strategy?: 'attribute' | 'class'; // Default: 'attribute'
enableAutoInit?: boolean; // Default: true
enableColorScheme?: boolean; // Default: true
enableSystem?: boolean; // Default: true
forcedTheme?: 'light' | 'dark' | 'system'; // Default: undefined
}
provideUiTheme({
strategy: 'class',
defaultTheme: 'system',
enableColorScheme: true
})
provideUiTheme({
storageKey: 'my-app-theme',
defaultTheme: 'dark'
})
provideUiTheme({
enableSystem: false,
defaultTheme: 'light'
})
provideUiTheme({
forcedTheme: 'dark',
enableAutoInit: true
})
The main service that manages theme state using Angular signals.
theme()
- Readonly signal for current theme settingsystemTheme()
- Readonly signal for system theme preferenceresolvedTheme()
- Computed signal for the actual applied theme
setTheme(theme: 'light' | 'dark' | 'system')
- Set the themetoggle()
- Cycle through themes (light β dark β system)isDark()
- Check if current theme is darkisLight()
- Check if current theme is lightisSystem()
- Check if using system themeisForced()
- Check if forced theme is activeinitialized
- Check if service is initialized
import { Component, inject } from '@angular/core';
import { ThemeService } from '@angularui/theme';
@Component({
selector: 'app-example',
template: `
<div>
<h1>Theme Demo</h1>
<div class="theme-info">
<p>Current setting: {{ themeService.theme() }}</p>
<p>System preference: {{ themeService.systemTheme() }}</p>
<p>Applied theme: {{ themeService.resolvedTheme() }}</p>
<p>Is dark mode: {{ themeService.isDark() ? 'Yes' : 'No' }}</p>
</div>
<div class="theme-controls">
<button (click)="themeService.setTheme('light')">Light</button>
<button (click)="themeService.setTheme('dark')">Dark</button>
<button (click)="themeService.setTheme('system')">System</button>
<button (click)="themeService.toggle()">Toggle</button>
</div>
</div>
`
})
export class ExampleComponent {
private themeService = inject(ThemeService);
}
provideUiTheme({
strategy: 'class'
})
/* CSS */
.dark {
--bg-color: #1f2937;
--text-color: #f9fafb;
}
<!-- HTML -->
<html class="dark">
<!-- Dark theme applied -->
</html>
provideUiTheme({
strategy: 'attribute'
})
/* CSS */
[data-theme="dark"] {
--bg-color: #1f2937;
--text-color: #f9fafb;
}
<!-- HTML -->
<html data-theme="dark">
<!-- Dark theme applied -->
</html>
The package automatically handles SSR scenarios:
- Server-side rendering - Uses default values for consistent rendering
- Hydration safety - Prevents mismatches between server and client
- Client-side activation - Loads saved preferences and applies them
- No additional configuration needed for Angular SSR
provideUiTheme({
enableAutoInit: false
})
// In your component
export class AppComponent implements OnInit {
private themeService = inject(ThemeService);
ngOnInit() {
// Initialize when ready
this.themeService.initialize();
}
}
provideUiTheme({
enableAutoInit: false
})
// Initialize based on conditions
ngOnInit() {
if (this.shouldInitializeTheme()) {
this.themeService.initialize();
}
}
import { effect, inject } from '@angular/core';
import { ThemeService } from '@angularui/theme';
// Listen to theme changes
effect(() => {
const themeService = inject(ThemeService);
const theme = themeService.resolvedTheme();
console.log('Theme changed to:', theme);
// Apply custom logic
if (theme === 'dark') {
// Dark theme specific logic
}
});
- Core package: ~3KB (gzipped)
- Zero dependencies - Only Angular core
- Tree-shakeable - Unused features are removed
We welcome contributions! Please see our Contributing Guide for details.
# Clone the repository
git clone https://github.com/angularcafe/angularui-theme.git
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run build
MIT License - see LICENSE file for details.
- Inspired by next-themes
- Built with Angular
Made with β€οΈ for the Angular community
Created by @immohammadjaved