{"id":51288169,"url":"https://github.com/theryansmee/ngx-command-palette","last_synced_at":"2026-06-30T08:01:55.548Z","repository":{"id":365026423,"uuid":"1267164932","full_name":"theryansmee/ngx-command-palette","owner":"theryansmee","description":"A keyboard-driven command palette for Angular. Auto-registers routes, custom commands, async providers, and fuzzy search.","archived":false,"fork":false,"pushed_at":"2026-06-26T08:32:45.000Z","size":347,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T10:15:49.245Z","etag":null,"topics":["angular","angular-component","angular22","cmd-k","cmdk","command-palette","fuzzy-search","keyboard-navigation","ngx","spotlight","typescript"],"latest_commit_sha":null,"homepage":"https://theryansmee.github.io/ngx-command-palette/","language":"TypeScript","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/theryansmee.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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-12T09:29:48.000Z","updated_at":"2026-06-26T08:32:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/theryansmee/ngx-command-palette","commit_stats":null,"previous_names":["theryansmee/ngx-command-palette"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/theryansmee/ngx-command-palette","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theryansmee%2Fngx-command-palette","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theryansmee%2Fngx-command-palette/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theryansmee%2Fngx-command-palette/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theryansmee%2Fngx-command-palette/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theryansmee","download_url":"https://codeload.github.com/theryansmee/ngx-command-palette/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theryansmee%2Fngx-command-palette/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34957627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"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-component","angular22","cmd-k","cmdk","command-palette","fuzzy-search","keyboard-navigation","ngx","spotlight","typescript"],"created_at":"2026-06-30T08:01:54.816Z","updated_at":"2026-06-30T08:01:55.522Z","avatar_url":"https://github.com/theryansmee.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @theryansmee/ngx-command-palette\n\n[![npm version](https://img.shields.io/npm/v/@theryansmee/ngx-command-palette.svg)](https://www.npmjs.com/package/@theryansmee/ngx-command-palette)\n[![npm downloads](https://img.shields.io/npm/dw/@theryansmee/ngx-command-palette.svg)](https://www.npmjs.com/package/@theryansmee/ngx-command-palette)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/theryansmee/ngx-command-palette/blob/main/LICENSE)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/@theryansmee/ngx-command-palette)](https://bundlephobia.com/package/@theryansmee/ngx-command-palette)\n[![Angular](https://img.shields.io/badge/Angular-22-dd0031)](https://angular.dev)\n[![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://theryansmee.github.io/ngx-command-palette/)\n\nA keyboard-driven command palette for Angular. Routes are auto-registered from your Router config - zero setup required. Add custom commands, async search providers, contextual visibility, and full keyboard navigation out of the box.\n\nInspired by tools like Linear, GitHub, and Raycast.\n\n## Features\n\n- **Auto-registers routes** - walks your Angular Router config and creates searchable commands from every route with a `title`\n- **Lazy-load aware** - re-scans routes as lazy modules load\n- **Async search providers** - register API-backed search sources with per-provider debounce and loading states\n- **Prefix routing** - scope providers behind prefixes (`@` for users, `#` for tickets) so they only fire when needed\n- **Contextual commands** - show or hide commands based on the current route or dynamic conditions\n- **Fuzzy search** - built-in scoring that ranks exact matches, prefix matches, word boundary matches, and character-by-character fuzzy matches\n- **Keyword search** - add extra search terms to any command\n- **Recent commands** - tracks recently used commands in localStorage with a configurable recency boost\n- **Priority boosting** - manually rank commands higher or lower\n- **Keyboard navigation** - Arrow keys, Enter, Escape, Tab - all handled\n- **Accessible** - follows the WAI-ARIA combobox pattern with `role=\"combobox\"`, `aria-activedescendant`, and focus trapping\n- **Custom item templates** - override row rendering globally or per-category with `ng-template`\n- **Fully themeable** - CSS custom properties for every visual aspect\n- **SSR-safe** - platform checks for `localStorage` and DOM APIs\n- **Standalone components** - no `NgModule` needed\n- **Signal-based** - reactive state using Angular signals\n\n## Angular Version Support\n\nEach Angular major version is maintained on its own branch:\n\n| Branch | Angular | Library | npm tag |\n|---|---|---|---|\n| `angular/19` | 19.x | 19.x.x | `angular19` |\n| `angular/20` | 20.x | 20.x.x | `angular20` |\n| `angular/21` | 21.x | 21.x.x | `angular21` |\n| `angular/22` | 22.x | 22.x.x | `latest` |\n\nThe `main` branch tracks the latest stable version.\n\nTo install a specific Angular version:\n\n```bash\nnpm install @theryansmee/ngx-command-palette@angular21\n```\n\n## Installation\n\n```bash\nng add @theryansmee/ngx-command-palette\n```\n\nThis automatically adds `provideCommandPalette()` to your app config, imports `CmdPaletteComponent`, and adds `\u003ccmd-palette /\u003e` to your root template.\n\nOr install manually:\n\n```bash\nnpm install @theryansmee/ngx-command-palette\n# or\nyarn add @theryansmee/ngx-command-palette\n# or\npnpm add @theryansmee/ngx-command-palette\n```\n\n### Peer Dependencies\n\n| Package | Version |\n|---------|---------|\n| `@angular/core` | `^22.0.0` |\n| `@angular/common` | `^22.0.0` |\n| `@angular/router` | `^22.0.0` |\n| `@angular/cdk` | `^22.0.0` |\n\n## Quick Start\n\n### 1. Provide the command palette\n\n```typescript\n// app.config.ts\nimport { ApplicationConfig } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { provideCommandPalette } from '@theryansmee/ngx-command-palette';\nimport { routes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideRouter(routes),\n    provideCommandPalette(),\n  ],\n};\n```\n\n### 2. Add the component to your root template\n\n```html\n\u003c!-- app.component.html --\u003e\n\u003ccmd-palette /\u003e\n\u003crouter-outlet /\u003e\n```\n\n### 3. Add titles to your routes\n\n```typescript\n// app.routes.ts\nimport { Routes } from '@angular/router';\n\nexport const routes: Routes = [\n  { path: 'dashboard', component: DashboardComponent, title: 'Dashboard' },\n  { path: 'settings', component: SettingsComponent, title: 'Settings' },\n  { path: 'profile', component: ProfileComponent, title: 'Profile' },\n];\n```\n\nThat's it. Press `Cmd+K` (or `Ctrl+K`) and all your titled routes are searchable.\n\n## Configuration\n\nPass a config object to `provideCommandPalette()` to customize behavior:\n\n```typescript\nprovideCommandPalette({\n  shortcut: 'mod.k',            // Keyboard shortcut to open (default: 'mod.k')\n  placeholder: 'Search...',     // Input placeholder text (default: 'Search or type a command...')\n  autoRegisterRoutes: true,     // Auto-register routes from Router config (default: true)\n  maxResults: 10,               // Maximum search results shown (default: 10)\n  trackRecent: true,            // Track recently used commands (default: false)\n  recentCount: 5,               // Number of recent commands to track (default: 5)\n  debounce: 150,                // Input debounce in milliseconds (default: 0)\n  animation: 'scale',           // Open animation: 'scale' | 'slide' | 'none' (default: 'scale')\n  theme: 'default',             // Built-in theme: 'default' | 'dark' | 'github' | 'linear'\n});\n```\n\n### Shortcut Format\n\nThe shortcut string uses dot-separated modifier keys followed by the key:\n\n| Shortcut | Keys |\n|----------|------|\n| `mod.k` | Cmd+K (Mac) / Ctrl+K (Windows/Linux) |\n| `meta.k` | Cmd+K (Mac) / Win+K (Windows) |\n| `ctrl.k` | Ctrl+K |\n| `ctrl.shift.p` | Ctrl+Shift+P |\n| `meta.shift.p` | Cmd+Shift+P |\n| `alt.shift.k` | Alt+Shift+K |\n\n## Route Configuration\n\n### Basic Routes\n\nRoutes with a `title` property are auto-registered with no extra config:\n\n```typescript\n{ path: 'dashboard', component: DashboardComponent, title: 'Dashboard' }\n// -\u003e Appears as \"Dashboard\" in the palette, navigates to /dashboard\n```\n\nRoutes without a `title` still get registered - the label is generated from the path:\n\n```typescript\n{ path: 'user-settings', component: UserSettingsComponent }\n// -\u003e Appears as \"User Settings\" in the palette\n```\n\n### Enriching Routes\n\nAdd a `commandPalette` object to `data` to customize how a route appears:\n\n```typescript\n{\n  path: 'settings/billing',\n  component: BillingComponent,\n  title: 'Billing',\n  data: {\n    commandPalette: {\n      label: 'Billing \u0026 Payments',           // Override the display label\n      category: 'Settings',                   // Override the default \"Pages\" category\n      keywords: ['invoice', 'payment', 'subscription'],  // Extra search terms\n      priority: 5,                            // Higher = appears first\n    },\n  },\n}\n```\n\n### Excluding Routes\n\nSet `commandPalette` to `false` to exclude a route:\n\n```typescript\n{\n  path: 'admin/debug',\n  component: DebugComponent,\n  title: 'Debug Panel',\n  data: { commandPalette: false },\n}\n```\n\n### Parameterized Routes\n\nRoutes with parameters (e.g. `:id`) are automatically skipped unless you provide an explicit `commandPalette` config:\n\n```typescript\n// This route is SKIPPED (has :id, no commandPalette config)\n{ path: 'users/:id', component: UserDetailComponent, title: 'User Detail' }\n\n// This route is INCLUDED (explicit config provided)\n{\n  path: 'users/:id',\n  component: UserDetailComponent,\n  title: 'User Detail',\n  data: {\n    commandPalette: {\n      label: 'View User',\n    },\n  },\n}\n```\n\n### Wildcard and Redirect Routes\n\nWildcard (`**`) and redirect (`redirectTo`) routes are always excluded automatically.\n\n### Child Routes\n\nChild routes are walked recursively and registered with their full path:\n\n```typescript\n{\n  path: 'admin',\n  component: AdminComponent,\n  title: 'Admin',\n  children: [\n    { path: 'users', component: AdminUsersComponent, title: 'Users' },\n    { path: 'roles', component: AdminRolesComponent, title: 'Roles' },\n  ],\n}\n// Registers: \"Admin\" (/admin), \"Users\" (/admin/users), \"Roles\" (/admin/roles)\n```\n\n### Lazy-Loaded Routes\n\nThe palette automatically re-scans routes when lazy modules are loaded. Routes inside `loadChildren` become available once the module has been loaded at least once.\n\n## Custom Commands\n\n### Registering Commands\n\nInject `CommandPaletteService` and call `register()` to add custom commands:\n\n```typescript\nimport { Component, inject, DestroyRef } from '@angular/core';\nimport { CommandPaletteService } from '@theryansmee/ngx-command-palette';\n\n@Component({ ... })\nexport class ProjectListComponent {\n  readonly #palette = inject(CommandPaletteService);\n  readonly #destroyRef = inject(DestroyRef);\n\n  constructor() {\n    this.#palette.register(\n      [\n        {\n          id: 'create-project',\n          label: 'Create New Project',\n          category: 'Actions',\n          shortcut: 'Cmd+N',\n          keywords: ['new', 'add'],\n          priority: 10,\n          action: () =\u003e this.openCreateDialog(),\n        },\n        {\n          id: 'export-csv',\n          label: 'Export Projects as CSV',\n          category: 'Actions',\n          action: () =\u003e this.exportService.exportCSV(),\n        },\n      ],\n      this.#destroyRef,  // Commands auto-deregister when the component is destroyed\n    );\n  }\n}\n```\n\n### Auto-Cleanup with DestroyRef\n\nWhen you pass a `DestroyRef` as the second argument to `register()`, the commands are automatically deregistered when the component or service is destroyed. This is the recommended approach for component-scoped commands.\n\n```typescript\n// Commands exist only while this component is alive\nthis.palette.register(commands, this.destroyRef);\n```\n\nWithout a `DestroyRef`, commands persist until manually deregistered or the app is destroyed.\n\n### Contextual Commands\n\nCommands can be scoped to specific routes or dynamic conditions using the `context` property:\n\n```typescript\nthis.palette.register(\n  [\n    {\n      id: 'delete-project',\n      label: 'Delete Project',\n      category: 'Danger',\n      action: () =\u003e this.deleteProject(),\n      context: {\n        routes: ['/projects/*'],       // Only visible on /projects/* pages\n        when: () =\u003e this.canDelete(),  // And only when the user has permission\n      },\n    },\n  ],\n  this.destroyRef,\n);\n```\n\nContext rules:\n\n- **`routes`** - an array of glob patterns matched against the current URL. Supports `*` (single segment) and `**` (any depth).\n- **`when`** - a function that returns `boolean`. Re-evaluated each time the palette opens or the query changes.\n- If both are provided, both must pass for the command to be visible.\n- Commands without a `context` are always visible.\n\n### Command Interface\n\n```typescript\ninterface Command {\n  id: string;                              // Unique identifier\n  label: string;                           // Display text\n  category?: string;                       // Group heading (e.g. \"Pages\", \"Actions\")\n  icon?: string;                           // Icon name or identifier\n  keywords?: string[];                     // Additional search terms\n  shortcut?: string;                       // Display-only shortcut hint (e.g. \"Cmd+N\")\n  action: () =\u003e void | Promise\u003cvoid\u003e;      // What happens when the command is executed\n  priority?: number;                       // Ranking boost (higher = appears first)\n  context?: {\n    routes?: string[];                     // Glob patterns for route visibility\n    when?: () =\u003e boolean;                  // Dynamic visibility check\n  };\n  data?: Record\u003cstring, unknown\u003e;          // Arbitrary metadata for custom templates\n}\n```\n\n## Async Search Providers\n\nRegister API-backed search sources that return results asynchronously. Results are merged with static commands and grouped by category.\n\n### Basic Provider (Universal)\n\nA provider without a `prefix` fires on every query:\n\n```typescript\nimport { Component, inject, DestroyRef } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { map } from 'rxjs';\nimport { CommandPaletteService } from '@theryansmee/ngx-command-palette';\n\n@Component({ ... })\nexport class AppComponent {\n  readonly #palette = inject(CommandPaletteService);\n  readonly #destroyRef = inject(DestroyRef);\n  readonly #http = inject(HttpClient);\n\n  constructor() {\n    this.#palette.registerProvider(\n      {\n        id: 'doc-search',\n        category: 'Documentation',\n        minQueryLength: 2,\n        debounce: 300,\n        search: (query) =\u003e this.#http.get\u003cDoc[]\u003e(`/api/docs?q=${query}`).pipe(\n          map(docs =\u003e docs.map(doc =\u003e ({\n            id: `doc:${doc.id}`,\n            label: doc.title,\n            action: () =\u003e window.open(doc.url),\n          }))),\n        ),\n      },\n      this.#destroyRef,\n    );\n  }\n}\n```\n\n### Prefixed Provider\n\nA provider with a `prefix` only fires when the user types that prefix. This prevents unnecessary API calls when you have many providers.\n\nWhen the user types a prefix character, it appears as a visual chip in the input and the placeholder updates to reflect the active provider. Pressing Backspace on an empty input exits prefix mode.\n\n```typescript\nthis.palette.registerProvider(\n  {\n    id: 'user-search',\n    category: 'Users',\n    prefix: '@',                    // Only fires when query starts with @\n    placeholder: 'Search users...', // Shown in the input when this prefix is active\n    emptyMessage: 'Start typing to search users...', // Shown when there are no results\n    minQueryLength: 2,\n    debounce: 300,\n    search: (query) =\u003e this.userService.search(query).pipe(\n      map(users =\u003e users.map(user =\u003e ({\n        id: `user:${user.id}`,\n        label: user.name,\n        icon: 'person',\n        action: () =\u003e this.router.navigate(['/users', user.id]),\n      }))),\n    ),\n  },\n  this.destroyRef,\n);\n\nthis.palette.registerProvider(\n  {\n    id: 'ticket-search',\n    category: 'Tickets',\n    prefix: '#',                    // Only fires when query starts with #\n    placeholder: 'Search tickets...',\n    emptyMessage: 'Start typing to search tickets...',\n    minQueryLength: 1,\n    debounce: 200,\n    search: (query) =\u003e this.ticketService.search(query).pipe(\n      map(tickets =\u003e tickets.map(ticket =\u003e ({\n        id: `ticket:${ticket.id}`,\n        label: `${ticket.key}: ${ticket.title}`,\n        action: () =\u003e this.router.navigate(['/tickets', ticket.id]),\n      }))),\n    ),\n  },\n  this.destroyRef,\n);\n```\n\nWith the above, typing `@john` only hits the user API, typing `#billing` only hits the ticket API, and typing `dashboard` only searches static commands. The prefix is stripped before being passed to the provider's `search` function.\n\nRegistered prefixes are automatically shown as hints in the palette footer.\n\n### SearchProvider Interface\n\n```typescript\ninterface SearchProvider {\n  id: string;                                        // Unique identifier\n  category: string;                                  // Group heading for results\n  search: (query: string) =\u003e Observable\u003cCommand[]\u003e;  // The search function\n  prefix?: string;                                   // Prefix trigger (e.g. '@', '#')\n  placeholder?: string;                              // Input placeholder when this prefix is active\n  emptyMessage?: string;                             // Message shown when no results (default: 'No results found.')\n  debounce?: number;                                 // Debounce in ms (default: 300)\n  minQueryLength?: number;                           // Minimum chars before searching (default: 1)\n  order?: number;                                    // Category sort order\n}\n```\n\n### Loading State\n\nThe palette shows a \"Searching...\" indicator while async providers are in-flight. You can also read the loading state programmatically:\n\n```typescript\nconst isLoading: boolean = this.palette.loading();\n```\n\n## Custom Item Templates\n\n\u003e Available from v22.0.1\n\nBy default, each result row renders an icon, label, and shortcut badge. For richer results (avatars, status badges, descriptions, etc.) you can provide custom templates that override how items are rendered while keeping all keyboard navigation, accessibility, and active-state behavior intact.\n\n### Global Template\n\nA template without a category value applies to all result items:\n\n```html\n\u003ccmd-palette\u003e\n  \u003cng-template cmdItemTemplate let-command let-active=\"active\"\u003e\n    \u003cimg [src]=\"command.data?.['avatar']\" class=\"avatar\" /\u003e\n    \u003cdiv class=\"details\"\u003e\n      \u003cspan\u003e{{ command.label }}\u003c/span\u003e\n      \u003cspan class=\"description\"\u003e{{ command.data?.['description'] }}\u003c/span\u003e\n    \u003c/div\u003e\n  \u003c/ng-template\u003e\n\u003c/cmd-palette\u003e\n```\n\n### Per-Category Template\n\nProvide a category name to scope the template to a specific group. Items in other categories fall back to the default rendering:\n\n```html\n\u003ccmd-palette\u003e\n  \u003cng-template cmdItemTemplate=\"Jira Issues\" let-command let-active=\"active\"\u003e\n    \u003cspan class=\"status-badge\" [class]=\"command.data?.['status']\"\u003e\n      {{ command.data?.['status'] }}\n    \u003c/span\u003e\n    \u003cspan\u003e{{ command.label }}\u003c/span\u003e\n    \u003cspan class=\"ticket-id\"\u003e{{ command.data?.['ticketId'] }}\u003c/span\u003e\n  \u003c/ng-template\u003e\n\n  \u003cng-template cmdItemTemplate=\"Team Members\" let-command let-active=\"active\"\u003e\n    \u003cimg [src]=\"command.data?.['avatar']\" class=\"avatar\" /\u003e\n    \u003cspan\u003e{{ command.label }}\u003c/span\u003e\n    \u003cspan class=\"role\"\u003e{{ command.data?.['role'] }}\u003c/span\u003e\n  \u003c/ng-template\u003e\n\u003c/cmd-palette\u003e\n```\n\n### Resolution Order\n\nWhen rendering an item, the palette resolves its template in this order:\n\n1. **Category-specific template** matching the item's `category`\n2. **Global template** (one with no category value)\n3. **Built-in default** (icon + label + shortcut)\n\n### Template Context\n\nEach template receives the following context variables:\n\n| Variable | Type | Access | Description |\n|----------|------|--------|-------------|\n| `$implicit` | `Command` | `let-command` | The command object for this row |\n| `active` | `boolean` | `let-active=\"active\"` | Whether this row is the currently selected item |\n\n### Using the `data` Property\n\nThe `data` property on `Command` is an optional `Record\u003cstring, unknown\u003e` for attaching arbitrary metadata that your custom templates can render. Search providers are the primary use case:\n\n```typescript\nthis.palette.registerProvider(\n  {\n    id: 'ticket-search',\n    category: 'Jira Issues',\n    search: (query) =\u003e this.ticketService.search(query).pipe(\n      map(tickets =\u003e tickets.map(ticket =\u003e ({\n        id: `ticket:${ticket.id}`,\n        label: ticket.title,\n        action: () =\u003e this.router.navigate(['/tickets', ticket.id]),\n        data: {\n          ticketId: ticket.key,\n          status: ticket.status,\n          assignee: ticket.assignee,\n        },\n      }))),\n    ),\n  },\n  this.destroyRef,\n);\n```\n\n### Importing the Directive\n\nThe `CmdItemTemplateDirective` is standalone. Import it in the component where you use `\u003ccmd-palette\u003e`:\n\n```typescript\nimport { CmdPaletteComponent, CmdItemTemplateDirective } from '@theryansmee/ngx-command-palette';\n\n@Component({\n  imports: [CmdPaletteComponent, CmdItemTemplateDirective],\n  // ...\n})\nexport class AppComponent {}\n```\n\n## Programmatic Control\n\n```typescript\nconst palette = inject(CommandPaletteService);\n\n// Open the palette\npalette.open();\n\n// Open with a pre-filled query\npalette.open('settings');\n\n// Close the palette\npalette.close();\n\n// Toggle open/closed\npalette.toggle();\n\n// Update the search query\npalette.updateQuery('dashboard');\n\n// Execute a command programmatically\npalette.execute(someCommand);\n\n// Read current state (signals)\nconst isOpen: boolean = palette.isOpen();\nconst query: string = palette.query();\nconst results: ScoredCommand[] = palette.results();\nconst isLoading: boolean = palette.loading();\n```\n\n## Keyboard Shortcuts\n\n| Key | Action |\n|-----|--------|\n| `Cmd+K` / `Ctrl+K` | Open the palette (configurable) |\n| `Escape` | Close the palette |\n| `Arrow Down` / `Tab` | Move selection down |\n| `Arrow Up` | Move selection up |\n| `Enter` | Execute the selected command |\n\n## Search \u0026 Ranking\n\nThe built-in search engine uses a multi-signal scoring approach:\n\n### Scoring Breakdown\n\n| Signal | Score Range | Description |\n|--------|-------------|-------------|\n| Exact label match | 100 | Query matches the label exactly |\n| Label starts with query | 80 | Label begins with the query |\n| Word boundary match | 60 | Query matches at a word boundary |\n| Fuzzy substring match | 40 | Characters appear in order within the label |\n| Fuzzy character match | 0-35 | Characters match with gaps (consecutive matches score higher) |\n| Keyword match | Capped below label | Keywords contribute but never outrank a label match |\n| Recent command boost | +4 to +20 | Recently used commands get a boost (most recent = highest) |\n| Priority boost | `priority * 10` | Manual priority multiplier |\n\nWhen the query is empty, commands are sorted by priority (highest first) and limited to `maxResults`.\n\n## Theming\n\n### Built-in Themes\n\nFour themes ship out of the box. Set via config for a permanent theme:\n\n```typescript\nprovideCommandPalette({\n  theme: 'github',   // 'default' | 'dark' | 'github' | 'linear'\n});\n```\n\nOr bind the `theme` input for dynamic runtime switching:\n\n```html\n\u003ccmd-palette [theme]=\"activeTheme()\" /\u003e\n```\n\nOr apply a theme via CSS class on any ancestor element:\n\n```html\n\u003cbody class=\"cmd-theme-dark\"\u003e\n  \u003ccmd-palette /\u003e\n\u003c/body\u003e\n```\n\nWhen using the CSS class approach, import the theme file in your global styles:\n\n```css\n@import '@theryansmee/ngx-command-palette/themes/dark.css';\n```\n\n| Theme | Description | CSS file |\n|-------|-------------|----------|\n| `default` | Clean light theme | n/a (built-in) |\n| `dark` | Neutral dark background with light text | `themes/dark.css` |\n| `github` | Dark theme inspired by GitHub's command palette | `themes/github.css` |\n| `linear` | Minimal dark theme inspired by Linear | `themes/linear.css` |\n\n### Animations\n\nThe palette opens with a configurable animation:\n\n```typescript\nprovideCommandPalette({\n  animation: 'scale',   // 'scale' | 'slide' | 'none'\n});\n\n// Or dynamically via input:\n// \u003ccmd-palette [animation]=\"activeAnimation()\" /\u003e\n```\n\nAnimations are automatically disabled when the user has `prefers-reduced-motion` enabled.\n\n### CSS Custom Properties\n\nThe palette uses CSS custom properties for full visual control. Override any variable on the `cmd-palette` selector or a parent element:\n\n```css\ncmd-palette {\n  /* Backdrop */\n  --cmd-backdrop: rgba(0, 0, 0, 0.5);\n\n  /* Dialog */\n  --cmd-bg: #ffffff;\n  --cmd-border: #e2e8f0;\n  --cmd-border-radius: 12px;\n  --cmd-shadow: 0 16px 70px rgba(0, 0, 0, 0.2);\n  --cmd-width: 640px;\n  --cmd-max-height: 400px;\n\n  /* Input */\n  --cmd-input-padding: 16px;\n  --cmd-input-font-size: 16px;\n  --cmd-input-color: #1a1a1a;\n  --cmd-input-placeholder: #64748b;\n\n  /* Prefix chip (shown when a prefixed provider is active) */\n  --cmd-prefix-chip-bg: #e2e8f0;\n  --cmd-prefix-chip-color: #475569;\n\n  /* Items */\n  --cmd-item-padding: 10px 16px;\n  --cmd-item-color: #334155;\n  --cmd-item-hover-bg: #f1f5f9;\n  --cmd-item-active-bg: #e2e8f0;\n\n  /* Group headings */\n  --cmd-group-heading-color: #64748b;\n  --cmd-group-heading-size: 12px;\n\n  /* Shortcut badges */\n  --cmd-shortcut-bg: #f1f5f9;\n  --cmd-shortcut-color: #64748b;\n  --cmd-shortcut-border: #e2e8f0;\n\n  /* Empty state */\n  --cmd-empty-color: #64748b;\n}\n```\n\n### Dark Theme Example\n\n```css\n.dark cmd-palette,\ncmd-palette.dark {\n  --cmd-backdrop: rgba(0, 0, 0, 0.7);\n  --cmd-bg: #1e1e2e;\n  --cmd-border: #313244;\n  --cmd-shadow: 0 16px 70px rgba(0, 0, 0, 0.5);\n  --cmd-input-color: #cdd6f4;\n  --cmd-input-placeholder: #6c7086;\n  --cmd-item-color: #cdd6f4;\n  --cmd-item-hover-bg: #313244;\n  --cmd-item-active-bg: #45475a;\n  --cmd-group-heading-color: #a6adc8;\n  --cmd-shortcut-bg: #313244;\n  --cmd-shortcut-color: #a6adc8;\n  --cmd-shortcut-border: #45475a;\n  --cmd-empty-color: #6c7086;\n}\n```\n\n## Accessibility\n\nThe palette follows the [WAI-ARIA combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) out of the box with no configuration needed. Focus trapping, focus restoration, screen reader announcements, keyboard navigation, and active item scrolling all work automatically.\n\n## API Reference\n\n### `provideCommandPalette(config?)`\n\nEnvironment provider factory. Call in your `appConfig.providers` array.\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `shortcut` | `string` | `'mod.k'` | Keyboard shortcut to open the palette |\n| `placeholder` | `string` | `'Search or type a command...'` | Input placeholder text |\n| `autoRegisterRoutes` | `boolean` | `true` | Auto-register routes from Router config |\n| `maxResults` | `number` | `10` | Maximum results shown |\n| `trackRecent` | `boolean` | `false` | Track recently used commands and boost them in results |\n| `recentCount` | `number` | `5` | Number of recent commands tracked (requires `trackRecent: true`) |\n| `debounce` | `number` | `0` | Input debounce in milliseconds |\n| `animation` | `'scale' \\| 'slide' \\| 'none'` | `'scale'` | Open animation style |\n| `theme` | `'default' \\| 'dark' \\| 'github' \\| 'linear'` | `'default'` | Built-in colour theme |\n\n### `CommandPaletteService`\n\nThe main service for interacting with the palette.\n\n| Method | Signature | Description |\n|--------|-----------|-------------|\n| `open` | `(initialQuery?: string) =\u003e void` | Opens the palette, optionally with a pre-filled query |\n| `close` | `() =\u003e void` | Closes the palette and clears the query |\n| `toggle` | `() =\u003e void` | Toggles the palette open/closed |\n| `updateQuery` | `(query: string) =\u003e void` | Updates the search query |\n| `execute` | `(command: Command) =\u003e void` | Executes a command, records it as recent, and closes |\n| `register` | `(commands: Command[], destroyRef?: DestroyRef) =\u003e void` | Registers static commands with optional auto-cleanup |\n| `registerProvider` | `(provider: SearchProvider, destroyRef?: DestroyRef) =\u003e void` | Registers an async search provider with optional auto-cleanup |\n\n| Signal | Type | Description |\n|--------|------|-------------|\n| `isOpen` | `Signal\u003cboolean\u003e` | Whether the palette is currently open |\n| `query` | `Signal\u003cstring\u003e` | The current search query (including prefix) |\n| `displayQuery` | `Signal\u003cstring\u003e` | The query with the active prefix stripped |\n| `results` | `Signal\u003cScoredCommand[]\u003e` | The current search results (scored and sorted) |\n| `loading` | `Signal\u003cboolean\u003e` | Whether any async provider is currently searching |\n| `activeProvider` | `Signal\u003cSearchProvider \\| null\u003e` | The currently active prefixed search provider |\n| `activePlaceholder` | `Signal\u003cstring\u003e` | The current input placeholder (provider-specific or default) |\n| `emptyMessage` | `Signal\u003cstring\u003e` | The current empty state message (provider-specific or default) |\n\n### `CmdPaletteComponent`\n\nThe root component. Add it once in your app root template.\n\n```html\n\u003ccmd-palette /\u003e\n```\n\n| Input | Type | Default | Description |\n|-------|------|---------|-------------|\n| `theme` | `CommandPaletteTheme` | config value | Override the theme for this instance |\n| `animation` | `CommandPaletteAnimation` | config value | Override the animation for this instance |\n\n**Content projection:** Place `\u003cng-template cmdItemTemplate\u003e` elements inside `\u003ccmd-palette\u003e` to customize item rendering. See [Custom Item Templates](#custom-item-templates).\n\n### `CmdItemTemplateDirective`\n\n\u003e Available from v22.0.1\n\nStructural directive used on `\u003cng-template\u003e` to define custom item templates.\n\n```html\n\u003cng-template cmdItemTemplate let-command let-active=\"active\"\u003e...\u003c/ng-template\u003e\n\u003cng-template cmdItemTemplate=\"Category Name\" let-command let-active=\"active\"\u003e...\u003c/ng-template\u003e\n```\n\n| Input | Type | Default | Description |\n|-------|------|---------|-------------|\n| `cmdItemTemplate` | `string` | `''` | Category name to scope the template to. Empty string applies to all items. |\n\n### `CmdItemTemplateContext`\n\n\u003e Available from v22.0.1\n\nThe type-safe context interface for custom item templates.\n\n```typescript\ninterface CmdItemTemplateContext {\n  $implicit: Command;  // Available via let-command (or any variable name)\n  active: boolean;     // Available via let-active=\"active\"\n}\n```\n\n## Migrating from `@ngxpert/cmdk`\n\nIf you're coming from `@ngxpert/cmdk` (or `@ngneat/cmdk`), this section maps the concepts you already know to their equivalents in `ngx-command-palette`.\n\n### Setup\n\nWith `@ngxpert/cmdk`, setup involves importing 7+ components and directives, building a custom template, wiring up Angular CDK Overlay for the dialog, and adding a `@HostListener` for the keyboard shortcut. With `ngx-command-palette`, the same result takes two lines:\n\n```typescript\n// app.config.ts\nprovideCommandPalette()\n\n// app.component.html\n\u003ccmd-palette /\u003e\n```\n\nThe overlay, keyboard shortcut, focus trapping, backdrop, and animations are all built in.\n\n### Component and Directive Mapping\n\n| `@ngxpert/cmdk` | `ngx-command-palette` | Notes |\n|---|---|---|\n| `\u003ccmdk-command\u003e` | `\u003ccmd-palette /\u003e` | Includes overlay, shortcut listener, and all subcomponents |\n| `\u003cinput cmdkInput\u003e` | Built-in | No separate import needed |\n| `\u003ccmdk-list\u003e` | Built-in | Results list is managed internally |\n| `\u003ccmdk-group label=\"...\"\u003e` | `category` on `Command` | Groups are created automatically from the `category` property |\n| `\u003cbutton cmdkItem\u003e` | `Command` object | Items are registered via `CommandPaletteService.register()` instead of template markup |\n| `\u003cdiv *cmdkEmpty\u003e` | Built-in | Customizable per-provider via `emptyMessage` |\n| `\u003ccmdk-separator\u003e` | Built-in | Automatic separators between category groups |\n| `\u003cdiv *cmdkLoader\u003e` | Built-in | Loading indicator shows automatically during async searches |\n| `[filter]` on `\u003ccmdk-command\u003e` | Built-in fuzzy scoring | Multi-signal ranking with exact, prefix, boundary, fuzzy, keyword, recency, and priority signals |\n\n### Defining Items\n\n`@ngxpert/cmdk` defines items as template markup with a directive:\n\n```html\n\u003ccmdk-group label=\"Actions\"\u003e\n  \u003cbutton cmdkItem [value]=\"'create'\" (selected)=\"onCreate()\"\u003eCreate Project\u003c/button\u003e\n  \u003cbutton cmdkItem [value]=\"'export'\" (selected)=\"onExport()\"\u003eExport CSV\u003c/button\u003e\n\u003c/cmdk-group\u003e\n```\n\n`ngx-command-palette` defines items as data objects registered through a service:\n\n```typescript\nthis.palette.register([\n  {\n    id: 'create',\n    label: 'Create Project',\n    category: 'Actions',\n    action: () =\u003e this.onCreate(),\n  },\n  {\n    id: 'export',\n    label: 'Export CSV',\n    category: 'Actions',\n    action: () =\u003e this.onExport(),\n  },\n], this.destroyRef);\n```\n\nThis data-driven approach enables features that template-based items cannot support: fuzzy search with scoring, automatic route registration, keyword matching, contextual visibility, recency tracking, and priority boosting.\n\n### Custom Filtering\n\n`@ngxpert/cmdk` accepts a custom filter function on the root component:\n\n```html\n\u003ccmdk-command [filter]=\"myFilter\"\u003e\n```\n\n`ngx-command-palette` does not expose a custom filter hook because the built-in search engine handles scoring across multiple signals (exact match, prefix, word boundary, fuzzy, keywords, recency, priority). For API-backed search, use [async search providers](#async-search-providers) instead.\n\n### Dialog and Keyboard Shortcut\n\n`@ngxpert/cmdk` requires you to wire up the dialog overlay and keyboard shortcut yourself, typically using Angular CDK Overlay and `@HostListener`:\n\n```typescript\n// @ngxpert/cmdk - manual setup required\n@HostListener('document:keydown.meta.k', ['$event'])\nopenDialog(event: KeyboardEvent): void {\n  event.preventDefault();\n  this.isOpen = true;\n}\n```\n\n`ngx-command-palette` handles this automatically. Configure the shortcut in the provider:\n\n```typescript\nprovideCommandPalette({ shortcut: 'mod.k' })\n```\n\n### Route Registration\n\nThis is the biggest difference. `@ngxpert/cmdk` has no concept of routes. Every item must be manually added to the template.\n\n`ngx-command-palette` auto-registers all titled routes from your Angular Router config. Set `autoRegisterRoutes: true` (the default) and every route with a `title` becomes searchable with zero additional code. See [Route Configuration](#route-configuration) for details.\n\n### What You Gain\n\nSwitching from `@ngxpert/cmdk` to `ngx-command-palette` gives you:\n\n- **Automatic route registration** with lazy-load awareness\n- **Fuzzy search with multi-signal scoring** instead of basic substring matching\n- **Async search providers** with per-provider debounce, prefix routing, and loading states\n- **Contextual commands** scoped to routes or dynamic conditions\n- **Recent command tracking** with recency boosting\n- **Built-in dialog, shortcut, and focus management** with no CDK Overlay boilerplate\n- **Four built-in themes** with full CSS custom property support\n- **WAI-ARIA combobox pattern** implemented out of the box\n- **Custom item templates** for rich result rendering\n- **Active maintenance** across Angular 19, 20, 21, and 22\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheryansmee%2Fngx-command-palette","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheryansmee%2Fngx-command-palette","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheryansmee%2Fngx-command-palette/lists"}