{"id":30988520,"url":"https://github.com/angularcafe/slateui-theme","last_synced_at":"2025-09-12T17:49:58.890Z","repository":{"id":304406102,"uuid":"1018686651","full_name":"angularcafe/slateui-theme","owner":"angularcafe","description":"Modern Theme Management for Angular - A lightweight, feature-rich theme library with automatic dark mode detection, SSR safe, and zero configuration required.","archived":false,"fork":false,"pushed_at":"2025-08-07T07:43:15.000Z","size":404,"stargazers_count":18,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-06T20:21:21.284Z","etag":null,"topics":["angular","angular-theme","angular-themes","angularui","angularui-theme","dark-mode","dark-theme","slateui","slateui-theme","theme-management","theme-manager","theme-switcher"],"latest_commit_sha":null,"homepage":"https://theme.slateui.dev","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/angularcafe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-12T20:05:13.000Z","updated_at":"2025-09-03T13:18:16.000Z","dependencies_parsed_at":"2025-07-12T23:21:36.223Z","dependency_job_id":"8927b5b8-1a09-492f-80ad-40df61f69e29","html_url":"https://github.com/angularcafe/slateui-theme","commit_stats":null,"previous_names":["angularcafe/angularui-theme","angularcafe/slateui-theme"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/angularcafe/slateui-theme","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angularcafe%2Fslateui-theme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angularcafe%2Fslateui-theme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angularcafe%2Fslateui-theme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angularcafe%2Fslateui-theme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/angularcafe","download_url":"https://codeload.github.com/angularcafe/slateui-theme/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angularcafe%2Fslateui-theme/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274848708,"owners_count":25360984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","angular-theme","angular-themes","angularui","angularui-theme","dark-mode","dark-theme","slateui","slateui-theme","theme-management","theme-manager","theme-switcher"],"created_at":"2025-09-12T17:49:51.735Z","updated_at":"2025-09-12T17:49:58.878Z","avatar_url":"https://github.com/angularcafe.png","language":"HTML","readme":"# @slateui/theme\n\n[![npm version](https://img.shields.io/npm/v/@slateui/theme.svg)](https://www.npmjs.com/package/@slateui/theme)\n[![Downloads](https://img.shields.io/npm/dm/@slateui/theme.svg)](https://www.npmjs.com/package/@slateui/theme)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nModern Theme Management for Angular - A lightweight, feature-rich theme library with automatic dark mode detection, SSR-safe, and zero configuration required.\n\n**🌐 [Live Demo](https://theme.slateui.dev)**\n\n## 🌟 Features\n\n- **🎨 Automatic Theme Detection** - Supports light, dark, and system themes with OS preference detection\n- **⚡ Angular 20 Signals** - Built with modern Angular signals for optimal performance and reactivity\n- **🖥️ SSR-safe** - No hydration mismatch, works with Angular SSR out of the box\n- **🎯 Zero Configuration** - Works out of the box with sensible defaults\n- **🔧 Flexible Strategy** - Choose between class-based or attribute-based theming\n- **📦 Tiny Bundle** - Lightweight with no unnecessary dependencies\n- **🛡️ Production Ready** - Comprehensive error handling and memory leak prevention\n- **♿ Accessibility Friendly** - Respects user preferences and system settings\n- **🚀 Performance Optimized** - Efficient DOM updates and minimal re-renders\n- **🔒 Type Safe** - Full TypeScript support with strict type checking\n- **🧪 Tested** - Comprehensive test coverage for reliability\n- **📚 Well Documented** - Extensive documentation with real-world examples\n- **⚙️ Modern Architecture** - Uses Angular's app initializer for clean, testable initialization\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @slateui/theme\n```\n\n### Basic Setup\n\nAdd the theme provider to your `app.config.ts`:\n\n```typescript\nimport { ApplicationConfig } from '@angular/core';\nimport { provideSlateUiTheme } from '@slateui/theme';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideSlateUiTheme()\n  ]\n};\n```\n\n### Use in Components\n\n```typescript\nimport { Component, inject } from '@angular/core';\nimport { ThemeService } from '@slateui/theme';\n\n@Component({\n  selector: 'app-header',\n  template: `\n    \u003cheader\u003e\n      \u003ch1\u003eMy App\u003c/h1\u003e\n      \u003cbutton (click)=\"toggleTheme()\"\u003eToggle Theme\u003c/button\u003e\n      \u003cp\u003eCurrent theme: {{ themeService.theme() }}\u003c/p\u003e\n      \u003cp\u003eResolved theme: {{ themeService.resolvedTheme() }}\u003c/p\u003e\n    \u003c/header\u003e\n  `\n})\nexport class HeaderComponent {\n  private themeService = inject(ThemeService);\n\n  toggleTheme() {\n    this.themeService.toggle();\n  }\n}\n```\n\n### Add CSS for Theming\n\n```css\n/* Default styles (light theme) */\n:root {\n  --bg-color: #ffffff;\n  --text-color: #000000;\n  --primary-color: #3b82f6;\n}\n\n/* Dark theme styles */\n.dark {\n  --bg-color: #1f2937;\n  --text-color: #f9fafb;\n  --primary-color: #60a5fa;\n}\n\nbody {\n  background-color: var(--bg-color);\n  color: var(--text-color);\n  transition: background-color 0.3s ease, color 0.3s ease;\n}\n```\n\n### How to Prevent Theme Flash (FOUC) with an Inline Script\nAdd this **inline** script to your `index.html` `\u003chead\u003e`:\n```html\n\u003cscript\u003e\n(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\u0026\u0026(n.classList.remove('light','dark'),n.removeAttribute('data-theme'),n.style.colorScheme='light')}catch(e){}}})();\n\u003c/script\u003e\n```\n**Why inline?** Angular does not provide a way to inject scripts into the HTML `\u003chead\u003e` 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.\n\n**Note:** This approach is SSR-safe: the initial HTML uses the default theme, and the correct theme is applied instantly on page load.\n\n#### FAQ: SSR, LocalStorage, and Theme Flash\n- The SSR HTML always uses the default theme, since user preferences are only available in the browser.\n- The inline script applies the correct theme instantly on page load, so users never see a flash of the wrong theme.\n- This is the standard, SSR-safe approach used by modern theme libraries (like next-themes).\n\n## Why @slateui/theme?\n\n- Native Angular integration: signals, DI, and standalone components\n- TypeScript-first and future-proof (Angular 20+ ready)\n- Clean, testable architecture (app initializer pattern)\n- Consistent, standardized theming across apps\n- Excellent developer experience (autocomplete, IDE support)\n- Performance optimized and tree-shakeable\n- Well-documented, maintainable, and enterprise-ready\n\n## 🏗️ Modern Architecture\n\n### App Initializer Pattern\n\n@slateui/theme uses Angular's `provideAppInitializer()` for clean, testable initialization:\n\n```typescript\n// Traditional approach (other libraries)\nconstructor() {\n  this.initialize(); // Side effects in constructor\n}\n\n// @slateui/theme approach\nprovideAppInitializer(() =\u003e {\n  const themeService = inject(ThemeService);\n  themeService.initialize(); // Clean, controlled initialization\n  return Promise.resolve();\n})\n```\n\n### Benefits of This Approach:\n\n- **🔄 Testable** - Can test service without auto-initialization\n- **⚡ Performant** - No constructor side effects\n- **🎯 Controlled** - Can conditionally initialize based on app state\n- **🧹 Clean** - Separation of concerns\n- **🔧 Flexible** - Manual initialization when needed\n- **📚 Modern** - Follows Angular 20+ best practices\n\n## 📖 Configuration Options\n\n```typescript\ninterface ThemeConfig {\n  defaultTheme?: 'light' | 'dark' | 'system';  // Default: 'system'\n  storageKey?: string;                         // Default: 'theme'\n  strategy?: 'attribute' | 'class';            // Default: 'attribute'\n  enableAutoInit?: boolean;                    // Default: true\n  enableColorScheme?: boolean;                 // Default: true\n  enableSystem?: boolean;                      // Default: true\n  forcedTheme?: 'light' | 'dark' | 'system';  // Default: undefined\n}\n```\n\n### Configuration Examples\n\n#### Tailwind CSS Integration\n```typescript\nprovideSlateUiTheme({\n  strategy: 'class'\n})\n```\n\n#### Custom Storage Key\n```typescript\nprovideSlateUiTheme({\n  storageKey: 'my-app-theme'\n})\n```\n\n#### Disable System Detection\n```typescript\nprovideSlateUiTheme({\n  enableSystem: false\n})\n```\n\n#### Forced Theme (for demos)\n```typescript\nprovideSlateUiTheme({\n  forcedTheme: 'dark'\n})\n```\n\n## 🔧 API Reference\n\n### ThemeService\n\nThe main service that manages theme state using Angular signals.\n\n#### Properties\n\n- `theme()` - Readonly signal for current theme setting\n- `systemTheme()` - Readonly signal for system theme preference\n- `resolvedTheme()` - Computed signal for the actual applied theme\n- `initialized` - Boolean property indicating if service is initialized\n- `isForced` - Boolean property indicating if forced theme is active\n\n#### Methods\n\n- `setTheme(theme: 'light' | 'dark' | 'system')` - Set the theme\n- `toggle()` - Cycle through themes (light → dark → system)\n- `isDark()` - Check if current theme is dark\n- `isLight()` - Check if current theme is light\n- `isSystem()` - Check if using system theme\n- `getConfig()` - Get current configuration\n- `cleanup()` - Manual cleanup (automatically called on destroy)\n\n### Example Usage\n\n```typescript\nimport { Component, inject } from '@angular/core';\nimport { ThemeService } from '@slateui/theme';\n\n@Component({\n  selector: 'app-example',\n  template: `\n    \u003cdiv\u003e\n      \u003ch1\u003eTheme Demo\u003c/h1\u003e\n      \n      \u003cdiv class=\"theme-info\"\u003e\n        \u003cp\u003eCurrent setting: {{ themeService.theme() }}\u003c/p\u003e\n        \u003cp\u003eSystem preference: {{ themeService.systemTheme() }}\u003c/p\u003e\n        \u003cp\u003eApplied theme: {{ themeService.resolvedTheme() }}\u003c/p\u003e\n        \u003cp\u003eIs dark mode: {{ themeService.isDark() ? 'Yes' : 'No' }}\u003c/p\u003e\n      \u003c/div\u003e\n\n      \u003cdiv class=\"theme-controls\"\u003e\n        \u003cbutton (click)=\"themeService.setTheme('light')\"\u003eLight\u003c/button\u003e\n        \u003cbutton (click)=\"themeService.setTheme('dark')\"\u003eDark\u003c/button\u003e\n        \u003cbutton (click)=\"themeService.setTheme('system')\"\u003eSystem\u003c/button\u003e\n        \u003cbutton (click)=\"themeService.toggle()\"\u003eToggle\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  `\n})\nexport class ExampleComponent {\n  private themeService = inject(ThemeService);\n}\n```\n\n## 🔄 Lifecycle Management\n\nThe ThemeService automatically handles cleanup when the application is destroyed. However, you can also manually manage the lifecycle:\n\n### Manual Cleanup\n\n```typescript\nimport { Component, inject, OnDestroy } from '@angular/core';\nimport { ThemeService } from '@slateui/theme';\n\n@Component({\n  selector: 'app-example',\n  template: `...`\n})\nexport class ExampleComponent implements OnDestroy {\n  private themeService = inject(ThemeService);\n\n  ngOnDestroy() {\n    // Manual cleanup (optional - automatic cleanup is handled)\n    this.themeService.cleanup();\n  }\n}\n```\n\n### Configuration Access\n\n```typescript\n// Get current configuration\nconst config = this.themeService.getConfig();\nconsole.log('Current config:', config);\n```\n\n## 🎨 Theming Strategies\n\n### Class Strategy (Recommended for Tailwind)\n\n```typescript\nprovideSlateUiTheme({\n  strategy: 'class'\n})\n```\n\n```css\n/* CSS */\n.dark {\n  --bg-color: #1f2937;\n  --text-color: #f9fafb;\n}\n```\n\n```html\n\u003c!-- HTML --\u003e\n\u003chtml class=\"dark\"\u003e\n  \u003c!-- Dark theme applied --\u003e\n\u003c/html\u003e\n```\n\n### Attribute Strategy (CSS Variables)\n\n```typescript\nprovideSlateUiTheme({\n  strategy: 'attribute'\n})\n```\n\n```css\n/* CSS */\n[data-theme=\"dark\"] {\n  --bg-color: #1f2937;\n  --text-color: #f9fafb;\n}\n```\n\n```html\n\u003c!-- HTML --\u003e\n\u003chtml data-theme=\"dark\"\u003e\n  \u003c!-- Dark theme applied --\u003e\n\u003c/html\u003e\n```\n\n## 🖥️ SSR Support\n\nThe package automatically handles SSR scenarios:\n\n- **Server-side rendering** - Uses default values for consistent rendering\n- **Hydration safety** - Prevents mismatches between server and client\n- **Client-side activation** - Loads saved preferences and applies them\n- **No additional configuration** needed for Angular SSR\n\n## 🚀 Advanced Usage\n\n### Manual Initialization\n\n```typescript\nprovideSlateUiTheme({\n  enableAutoInit: false\n})\n\n// In your component\nexport class AppComponent implements OnInit {\n  private themeService = inject(ThemeService);\n  \n  ngOnInit() {\n    // Initialize when ready\n    this.themeService.initialize();\n  }\n}\n```\n\n### Conditional Initialization\n\n```typescript\nprovideSlateUiTheme({\n  enableAutoInit: false\n})\n\n// Initialize based on conditions\nngOnInit() {\n  if (this.shouldInitializeTheme()) {\n    this.themeService.initialize();\n  }\n}\n```\n\n### Custom Theme Detection\n\n```typescript\nimport { effect, inject } from '@angular/core';\nimport { ThemeService } from '@slateui/theme';\n\n// Listen to theme changes\neffect(() =\u003e {\n  const themeService = inject(ThemeService);\n  const theme = themeService.resolvedTheme();\n  console.log('Theme changed to:', theme);\n  \n  // Apply custom logic\n  if (theme === 'dark') {\n    // Dark theme specific logic\n  }\n});\n```\n\n## 📦 Bundle Size\n\n- **Core package**: ~13KB (raw) / ~3KB (gzipped)\n- **Zero external dependencies** - Only Angular core and common\n- **Tree-shakeable** - Unused features are removed\n\n## 🤝 Contributing\n\nContributions are welcome! To contribute:\n\n1. **Fork** this repository.\n2. **Create a new branch** for your feature or fix.\n3. **Make your changes** and ensure all tests pass.\n4. **Open a Pull Request** with a clear description of your changes.\n\nPlease review our [Contributing Guide](CONTRIBUTING.md) before submitting your PR.\n\n## 📄 License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Inspired by [next-themes](https://github.com/pacocoursey/next-themes)\n- Built with [Angular](https://angular.io/)\n\n---\n\n**Made with ❤️ for the Angular community**\n\n**Created by [@immohammadjaved](https://x.com/immohammadjaved)**\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangularcafe%2Fslateui-theme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fangularcafe%2Fslateui-theme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangularcafe%2Fslateui-theme/lists"}