ngx-theme-stack
v3.10.0
Published
A stack of themes for Angular applications.
Maintainers
Readme
🎨 ngx-theme-stack
A simple and powerful headless theme manager for Angular.
Built for performance, signal-driven reactivity, and SSR zero-flash support.
🌐 Live Demo · ⚡ StackBlitz Demo · 📚 Documentation · ⭐ Star on GitHub

🚀 Features
- ⚡ Single Command Setup: Fully configured via standard
ng add ngx-theme-stack. - 🌓 OS Preference Auto-Detection: Automatically synchronizes with system dark/light modes.
- 🧱 Angular Signals: Built natively with reactive signals for maximum performance.
- 🌍 SSR & Hydration Ready: Guaranteed zero flicker or layout shift on startup.
- 🔄 Extensible & Custom Themes: Seamlessly add sepia, sunset, or custom color palettes.
- 🎛️ Per-Theme Color Scheme Hints: Declara el
color-schemepara cada custom theme y deja que los widgets nativos del browser (scrollbars, inputs) coincidan con el tema activo. - 🤖 AI Code Assistants Integration: Work with AI coding assistants using the generated
SKILL.mdstandard.
📦 Installation
ng add ngx-theme-stack[!TIP] Using Bun? Since
ng addis not supported in Bun environments, use the manual two-step process:bun add ngx-theme-stack ng generate ngx-theme-stack:ng-add
[!TIP] Using an Nx monorepo? Since
ng adddoesn't run in Nx workspaces, use the Nx-native two-step process:nx add ngx-theme-stack nx g ngx-theme-stack:ng-add --project <app-name>
🛠️ Quick Start
Below is a simple example of a standalone theme toggle component.
[!TIP] 💡 Architecture Best Practice: Isolate the theme switcher logic (using
ThemeToggleService,ThemeCycleService, orThemeSelectService) in dedicated switcher components rather than injecting them directly into the rootAppComponent.This keeps your root component clean, respects the single-responsibility principle, and ensures that theme-switching UI elements are highly reusable. Only inject the service at the root level if the root UI structure itself needs to dynamically react to theme changes (e.g., for dynamic logo assets or theme-dependent styles not covered by CSS variables).
import { inject, Component } from '@angular/core';
import { ThemeToggleService } from 'ngx-theme-stack';
@Component({
selector: 'app-theme-toggle',
template: `
@if (theme.isHydrated()) {
<button (click)="theme.toggle()">
{{ theme.isDark() ? '🌙' : '☀️' }}
</button>
} @else {
<!-- Placeholder matching dimensions to prevent layout shifts -->
<div
class="theme-toggle-skeleton"
style="width: 40px; height: 40px; background: #e2e8f0; border-radius: 4px;"
></div>
}
`,
})
export class ThemeToggle {
protected readonly theme = inject(ThemeToggleService);
}🎛️ Per-Theme Color Scheme Hints
When you use custom themes, the library can also apply a color-scheme hint to the root element so that native UI widgets (scrollbars, form controls) match the active theme.
// Legacy: string form — no hint on custom themes
provideThemeStack({
themes: ['sepia', 'ocean'] as const,
})
// New: object form — explicit hint per custom theme
provideThemeStack({
themes: [
{ name: 'sepia', scheme: 'light' },
{ name: 'ocean', scheme: 'dark' },
] as const,
})Both shapes can be mixed in the same array:
provideThemeStack({
themes: ['light', 'dark', 'system', { name: 'forest' }] as const,
// 'light' and 'dark' keep their implicit hint; 'forest' defaults to 'auto' (no hint)
})scheme values:
| Value | Effect on color-scheme |
|---------|-------------------------------------|
| 'light' | Sets color-scheme: light on the root element while the theme is active. |
| 'dark' | Sets color-scheme: dark. |
| 'auto' | No hint is set; the browser decides, usually from the OS. Also the default when scheme is omitted. |
Any other value is invalid and falls back to 'auto'. The hint is applied to <html> in the runtime and in the anti-flash script (before Angular boots), and is regenerated by the sync schematic — which drops an invalid scheme and warns about it instead of writing it back.
🏷️ Theme Names
A theme name becomes a CSS class (.name) in class mode and is written into
classList, so it has to be usable as a CSS identifier. Names must match:
/^-?[a-zA-Z_][a-zA-Z0-9_-]*$/| Name | Accepted | Why |
|------|----------|-----|
| lucky, lucky-theme, lucky_theme, lucky2, _lucky, -lucky | ✅ | usable as-is |
| 2lucky | ❌ | .2lucky is invalid CSS, so the browser would drop the whole rule and the theme would silently lose its styles |
| lucky theme | ❌ | classList.add throws InvalidCharacterError on ASCII whitespace |
| lucky.theme | ❌ | .lucky.theme matches two classes, but the class token is the single string lucky.theme, so the styles would never apply — with no error at all |
| sueño, café | ❌ | deliberately ASCII-only. Valid CSS, but a narrower subset is easier to reason about; rejected on purpose, not by accident |
provideThemeStack() throws NgxThemeStackError for a name that does not match,
so a bad name is a build error instead of a silent first-paint flash. The
ng-add prompt re-asks with the reason, and sync reports invalid names it finds
in an existing config.
The same pattern is inline in the anti-flash script, which validates the untrusted
localStoragevalue before writing it to the DOM.
📚 Advanced Guides & Documentation
For details on more advanced topics, check out the official guides:
- ⚙️ Getting Started & Configuration - Detailed setup options and configuration properties.
- 🎨 Styling & Custom Themes - Learn how to define CSS variables for custom themes.
- 🌪️ Tailwind CSS v4 Integration - Map variables natively to Tailwind v4 theme configurations.
- ⚡ Performance & Anti-Flash Strategies - Learn how the zero-flash system works and how to choose between
crittersandblockingmodes. - 🤖 AI Agent Skill Setup - Work with AI code assistants using the generated
SKILL.mdstandard. - 🛡️ API Reference - Deep dive into convenience services, methods, and
CoreThemeServicesignals.
🤝 Contributing & Issues
Contributions are welcome! If you find a bug or have a suggestion, feel free to open an issue or submit a pull request:
- Report bugs or request features on GitHub Issues.
- For local development guidelines (how to build, test, and run the demo), please refer to the workspace README.
