npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ngx-theme-stack

v3.10.0

Published

A stack of themes for Angular applications.

Readme

🎨 ngx-theme-stack

A simple and powerful headless theme manager for Angular.
Built for performance, signal-driven reactivity, and SSR zero-flash support.

npm version license angular signals SSR AI Skill

🌐 Live Demo · ⚡ StackBlitz Demo · 📚 Documentation · ⭐ Star on GitHub

ngx-theme-stack banner


🚀 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-scheme para 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.md standard.

📦 Installation

ng add ngx-theme-stack

[!TIP] Using Bun? Since ng add is 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 add doesn'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, or ThemeSelectService) in dedicated switcher components rather than injecting them directly into the root AppComponent.

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 localStorage value before writing it to the DOM.


📚 Advanced Guides & Documentation

For details on more advanced topics, check out the official guides:


🤝 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.

📄 License

MIT