@angularui/theme
⚠️ DEPRECATED: This package has been rebranded to @slateui/theme
@angularui/theme is deprecated and will no longer receive updates. Please migrate to the new package:
npm uninstall @angularui/theme npm install @slateui/theme
Quick Migration:
- Replace all imports from
@angularui/theme
to@slateui/theme
- Update provider from
provideUiTheme()
toprovideSlateUiTheme()
- Update your
app.config.ts
and component importsNew Repository: https://github.com/angularcafe/slateui-theme
Documentation: https://github.com/angularcafe/slateui-theme#readme
Modern Theme Management for Angular - A lightweight, feature-rich theme library with automatic dark mode detection, SSR-safe, and zero configuration required.
🌟 Features
- 🎨 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-safe - No hydration mismatch, works with Angular SSR out of the box
- 🎯 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
🚀 Quick Start
Installation
npm install @angularui/theme
Basic Setup
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()
]
};
Use in Components
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();
}
}
Add CSS for Theming
/* 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;
}
How to Prevent Theme Flash (FOUC) with an Inline Script
Add this inline script to your index.html
<head>
:
<script>
(function(){'use strict';try{var t=localStorage.getItem('theme')||'system',e=t==='system'?window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light':t==='light'||t==='dark'?t:'light',n=document.documentElement;if(n){n.classList.remove('light','dark'),e==='dark'?(n.classList.add('dark'),n.setAttribute('data-theme','dark')):(n.classList.remove('dark'),n.removeAttribute('data-theme')),n.style.colorScheme=e}}catch(e){try{var n=document.documentElement;n&&(n.classList.remove('light','dark'),n.removeAttribute('data-theme'),n.style.colorScheme='light')}catch(e){}}})();
</script>
Why inline? Angular does not provide a way to inject scripts into the HTML <head>
at build time. For true FOUC prevention, the script must run immediately as the HTML is parsed—before any content is rendered. External scripts or Angular providers/services run too late to prevent a flash. This is why the script must be copied directly into your index.html
head.
Note: This approach is SSR-safe: the initial HTML uses the default theme, and the correct theme is applied instantly on page load.
FAQ: SSR, LocalStorage, and Theme Flash
- The SSR HTML always uses the default theme, since user preferences are only available in the browser.
- The inline script applies the correct theme instantly on page load, so users never see a flash of the wrong theme.
- This is the standard, SSR-safe approach used by modern theme libraries (like next-themes).
Why @angularui/theme?
- Native Angular integration: signals, DI, and standalone components
- TypeScript-first and future-proof (Angular 20+ ready)
- Clean, testable architecture (app initializer pattern)
- Consistent, standardized theming across apps
- Excellent developer experience (autocomplete, IDE support)
- Performance optimized and tree-shakeable
- Well-documented, maintainable, and enterprise-ready
🏗️ Modern Architecture
App Initializer Pattern
@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();
})
Benefits of This Approach:
- 🔄 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
📖 Configuration Options
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
}
Configuration Examples
Tailwind CSS Integration
provideUiTheme({
strategy: 'class'
})
Custom Storage Key
provideUiTheme({
storageKey: 'my-app-theme'
})
Disable System Detection
provideUiTheme({
enableSystem: false
})
Forced Theme (for demos)
provideUiTheme({
forcedTheme: 'dark'
})
🔧 API Reference
ThemeService
The main service that manages theme state using Angular signals.
Properties
theme()
- Readonly signal for current theme settingsystemTheme()
- Readonly signal for system theme preferenceresolvedTheme()
- Computed signal for the actual applied themeinitialized
- Boolean property indicating if service is initializedisForced
- Boolean property indicating if forced theme is active
Methods
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 themegetConfig()
- Get current configurationcleanup()
- Manual cleanup (automatically called on destroy)
Example Usage
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);
}
🔄 Lifecycle Management
The ThemeService automatically handles cleanup when the application is destroyed. However, you can also manually manage the lifecycle:
Manual Cleanup
import { Component, inject, OnDestroy } from '@angular/core';
import { ThemeService } from '@angularui/theme';
@Component({
selector: 'app-example',
template: `...`
})
export class ExampleComponent implements OnDestroy {
private themeService = inject(ThemeService);
ngOnDestroy() {
// Manual cleanup (optional - automatic cleanup is handled)
this.themeService.cleanup();
}
}
Configuration Access
// Get current configuration
const config = this.themeService.getConfig();
console.log('Current config:', config);
🎨 Theming Strategies
Class Strategy (Recommended for Tailwind)
provideUiTheme({
strategy: 'class'
})
/* CSS */
.dark {
--bg-color: #1f2937;
--text-color: #f9fafb;
}
<!-- HTML -->
<html class="dark">
<!-- Dark theme applied -->
</html>
Attribute Strategy (CSS Variables)
provideUiTheme({
strategy: 'attribute'
})
/* CSS */
[data-theme="dark"] {
--bg-color: #1f2937;
--text-color: #f9fafb;
}
<!-- HTML -->
<html data-theme="dark">
<!-- Dark theme applied -->
</html>
🖥️ SSR Support
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
🚀 Advanced Usage
Manual Initialization
provideUiTheme({
enableAutoInit: false
})
// In your component
export class AppComponent implements OnInit {
private themeService = inject(ThemeService);
ngOnInit() {
// Initialize when ready
this.themeService.initialize();
}
}
Conditional Initialization
provideUiTheme({
enableAutoInit: false
})
// Initialize based on conditions
ngOnInit() {
if (this.shouldInitializeTheme()) {
this.themeService.initialize();
}
}
Custom Theme Detection
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
}
});
📦 Bundle Size
- Core package: ~13KB (raw) / ~3KB (gzipped)
- Zero external dependencies - Only Angular core and common
- Tree-shakeable - Unused features are removed
🤝 Contributing
Contributions are welcome! To contribute:
- Fork this repository.
- Create a new branch for your feature or fix.
- Make your changes and ensure all tests pass.
- Open a Pull Request with a clear description of your changes.
Please review our Contributing Guide before submitting your PR.
📄 License
MIT License - see LICENSE file for details.
🙏 Acknowledgments
- Inspired by next-themes
- Built with Angular
Made with ❤️ for the Angular community
Created by @immohammadjaved