@su-labs/theme
v21.0.5
Published
`@su-labs/theme`
Downloads
194
Maintainers
Readme
SuThemeService
@su-labs/theme
A robust and reactive Angular service for managing application themes. It provides a full-featured solution that supports dynamic switching, user preference persistence, and automatic system theme detection.
📚 Table of Contents
- Why Use This Library?
- Features
- Available Themes
- Installation
- Quick Start
- Usage
- API Reference
- Configuration Options
- Browser Compatibility
- Related Libraries
- Troubleshooting
- Support
- Contributing
- License
💡 Why Use This Library?
- 🎯 Lightweight - Minimal bundle size
- ⚡ Reactive - Built with Angular Signals
- 🎨 CSS Variables - Modern CSS custom properties
- 🌓 System Theme - Auto-detects OS preference
- 💾 Persistent - Remembers user choice
- 🔒 Type-Safe - Full TypeScript support
- 📦 Zero Dependencies - No bloat
- 🧪 Well Tested - Comprehensive test coverage
Features
- Reactive State: Uses Angular signals for efficient, reactive theme management.
- System Theme Integration: Automatically follows the user's system theme preference (
prefers-color-scheme). - Persistence: Remembers the user's last-selected theme using
localStorage. - Configurable: Customizable with a configuration object for storage keys, CSS variable prefixes, and default themes.
- Programmatic Control: Simple public API to set, get, and track the active theme.
Available Themes
The service currently supports the following theme names by default:
light- Light themedark- Dark themecontrast- High contrast themecustom- Custom theme with your own variablessystem- Automatically follows OS preference
You can provide custom variables for any of these themes via the themes property in SuThemeConfig.
Installation
You can install this library via npm:
npm install @su-labs/themePeer Dependencies
This library requires the following peer dependencies:
npm install @angular/common@^21 @angular/core@^21🚀 Quick Start
// 1. Install
npm install @su-labs/theme
// 2. Initialize in app.config.ts
import { provideAppInitializer, inject } from '@angular/core';
import { SuThemeService } from '@su-labs/theme';
export const appConfig = {
providers: [
provideAppInitializer(() => {
inject(SuThemeService).init();
}),
],
};
// 3. Use in component
import { Component, inject } from '@angular/core';
import { SuThemeService } from '@su-labs/theme';
@Component({
template: `
<button (click)="themeService.setTheme('dark')">Dark Mode</button>
<p>Current: {{ themeService.theme() }}</p>
`
})
export class AppComponent {
themeService = inject(SuThemeService);
}Usage
The SuThemeService must be initialized at application startup to load its configuration and initial theme.
Example 1: Global Initialization with provideAppInitializer
// main.ts or app.config.ts
import { provideAppInitializer, inject } from '@angular/core';
import { SuThemeService, SuThemeConfig } from '@su-labs/theme';
const myThemeConfig: SuThemeConfig = {
// Optional: Set a default theme if none is saved
defaultTheme: 'dark',
// Optional: Provide custom CSS variables for any theme
themes: {
dark: {
color: '#333',
background: '#eee',
'main-accent': '#007bff',
},
},
};
export const appConfig = {
providers: [
provideAppInitializer(() => {
const themeService = inject(SuThemeService);
themeService.init(myThemeConfig);
}),
],
};✅ This ensures the theme is loaded and applied before the application renders. The SuThemeService handles all the logic internally, including loading from localStorage and applying the initial theme.
Example 2: Using the Theme in Your Styles
Your service automatically applies CSS variables to the :root element. Your application stylesheets can use these variables, and the service will handle updating their values when the theme changes.
/* styles.css */
/* Use the CSS variables in your stylesheet */
body {
color: var(--su-theme-color);
background-color: var(--su-theme-background);
}
h1 {
color: var(--su-theme-main-accent);
}The service dynamically updates these variables based on the active theme, applying a combination of its internal defaults and any custom overrides you provide in the configuration.
Example 3: Dynamic Theme Switching
Use the setTheme() method to switch themes and the theme signal to read the current state.
import { Component, inject } from '@angular/core';
import { SuThemeService, SuThemeName } from '@su-labs/theme';
@Component({
selector: 'app-theme-switcher',
standalone: true,
imports: [],
template: `
<h1>Dynamic Theme Switching</h1>
<p>Current Theme: {{ themeService.theme() }}</p>
<button (click)="setTheme('light')">Light</button>
<button (click)="setTheme('dark')">Dark</button>
<button (click)="setTheme('system')">System</button>
`,
})
export class ThemeSwitcherComponent {
public readonly themeService = inject(SuThemeService);
setTheme(themeName: SuThemeName) {
this.themeService.setTheme(themeName);
}
}✅ This correctly shows how to use your service's public API to switch between themes. The service handles the underlying logic, including persistence and CSS variable application.
📖 API Reference
Signals
| Signal | Type | Description |
|--------|------|-------------|
| theme() | SuThemeName | Current active theme name |
Methods
| Method | Parameters | Description |
|--------|------------|-------------|
| init(config?) | SuThemeConfig | Initialize with custom configuration |
| setTheme(theme, persist?) | SuThemeName, boolean | Change theme (persist defaults to true) |
| getTheme() | - | Returns current theme name |
Types
type SuThemeName = 'light' | 'dark' | 'contrast' | 'custom' | 'system';
interface SuThemeConfig {
storageKey?: string; // Default: 'su:theme'
cssVarPrefix?: string; // Default: '--su-theme-'
defaultTheme?: SuThemeName; // Default: 'system'
themes?: {
[key in SuThemeName]?: Record<string, string>;
};
}Configuration Options
The service can be configured to fit your application's needs.
| Option | Description | Default Value |
|--------------------|---------------------------------------------------------------|------------------|
| storageKey | The key used to persist the selected theme in localStorage. | 'su:theme' |
| cssVarPrefix | The prefix for all CSS variables managed by the service. | '--su-theme-' |
| defaultTheme | The theme to use if no preference is saved in localStorage. | 'system' |
| themes | An object with custom CSS variable overrides for each theme. | undefined |
🌐 Browser Compatibility
Works in all modern browsers that support:
- ✅ CSS Custom Properties
- ✅ localStorage API
- ✅ matchMedia (for system theme)
- ✅ Angular 21+
- ✅ ES2022+
| Browser | Version | |---------|---------| | Chrome | ≥ 90 | | Firefox | ≥ 88 | | Safari | ≥ 14 | | Edge | ≥ 90 |
🔗 Related Libraries
Part of the @su-labs suite:
- 👀 @su-labs/visit-tracker - User visit tracking
- 🔔 @su-labs/notifications - Toast notification system
- 🔍 @su-labs/scroll-spy - Scroll position tracking
- 📏 @su-labs/font-size - Accessible font size controls
🔧 Troubleshooting
Theme not persisting?
Make sure localStorage is enabled in your browser and not blocked by privacy settings.
CSS variables not updating?
Ensure you're using the correct prefix in your CSS (--su-theme- by default) and that you've initialized the service.
System theme not working?
Check that your OS has a theme preference set and your browser supports prefers-color-scheme media query.
TypeScript errors?
Ensure you have Angular 21+ and TypeScript 5.7+ installed.
💬 Support
- 🐛 Found a bug? Open an issue
- 💡 Have a feature request? Start a discussion
- ⭐ Like this library? Give it a star!
Notes
- The service safely handles properties and ignores null or undefined values, preventing issues like prototype pollution.
- Works seamlessly with standalone components and reactive setups thanks to its signal-based API.
- Persistence is handled automatically: The service saves the user's preference to
localStoragewhen you callsetTheme(..., true). On initialization, it automatically restores this saved preference. You do not need to add this logic in your components.
Contributing
If you find any bugs or have feature requests, please open an issue or submit a pull request on our GitHub repository.
To contribute code, please ensure your changes include unit tests to maintain code quality. Please see the main repository's README.md for details on the monorepo structure.
License
This project is licensed under the MIT License. See the LICENSE file for details.
