Gathering detailed insights and metrics for @ngxpert/cmdk
Gathering detailed insights and metrics for @ngxpert/cmdk
Gathering detailed insights and metrics for @ngxpert/cmdk
Gathering detailed insights and metrics for @ngxpert/cmdk
cmdk
<p align="center"> <img src="./website/public/og.png" /> </p>
cmdk-sv
<p align="center"> <img src="./static/og.png" /> </p>
@ngxpert/hot-toast
Smoking hot Notifications for Angular. Lightweight, customizable and beautiful by default.
react-cmdk
A fast, accessible, and pretty React.js command palette
Fast, composable, unstyled command menu for Angular. Directly inspired from pacocoursey/cmdk
npm install @ngxpert/cmdk
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
45 Stars
111 Commits
1 Forks
1 Watching
2 Branches
3 Contributors
Updated on 27 Nov 2024
TypeScript (67.01%)
SCSS (21.21%)
HTML (9.37%)
JavaScript (2.36%)
Shell (0.05%)
Cumulative downloads
Total Downloads
Last day
-55%
154
Compared to previous day
Last week
-22%
1,228
Compared to previous week
Last month
63.8%
6,013
Compared to previous month
Last year
0%
23,073
Compared to previous year
2
Fast, composable, unstyled command menu for Angular. Directly inspired from pacocoursey/cmdk
@ngxpert/cmdk is a command menu Angular component that can also be used as an accessible combobox. You render items, it filters and sorts them automatically. @ngxpert/cmdk supports a fully composable API, so you can wrap items in other components or even as static HTML.
Demo and examples: ngxpert.github.io/cmdk
@ngxpert/cmdk | Angular |
---|---|
1.x | >=16 <17 |
2.x | >=17 <18 |
3.x | >=18 |
You can use @ngneat/cmdk
package from npm.
1## First, install dependencies 2 3## For Angular v16 4npm install @ngneat/overview@5 @ngneat/until-destroy@10 @angular/cdk@16 5 6## For Angular v17 7npm install @ngneat/overview@6 @ngneat/until-destroy@10 @angular/cdk@17 8 9## Then library 10npm install @ngxpert/cmdk
Same as npm
, just instead of npm install
, write yarn add
.
This is taken care with ng add @ngxpert/cmdk
1import { CmdkModule } from '@ngxpert/cmdk'; 2 3@NgModule({ 4 imports: [ 5 CmdkModule, 6 ], 7}) 8export class AppModule {}
1import { AppComponent } from './src/app.component'; 2 3import { 4 CommandComponent, 5 GroupComponent, 6 InputDirective, 7 ItemDirective, 8 ListComponent, 9 EmptyDirective 10} from '@ngxpert/cmdk'; 11 12@Component({ 13 selector: 'app-root', 14 standalone: true, 15 imports: [ 16 CommandComponent, 17 InputDirective, 18 ListComponent, 19 GroupComponent, 20 ItemDirective, 21 EmptyDirective 22 ], 23})
1<cmdk-command> 2 <input cmdkInput /> 3 <div *cmdkEmpty>No results found.</div> 4 <cmdk-list> 5 <cmdk-group label="Letters"> 6 <button cmdkItem>a</button> 7 <button cmdkItem>b</button> 8 <cmdk-separator></cmdk-separator> 9 <button cmdkItem>c</button> 10 </cmdk-group> 11 </cmdk-list> 12 13 <button cmdkItem>Apple</button> 14</cmdk-command>
Each component has a specific class (starting with cmdk-
) that can be used for styling.
Render this to show the command menu.
Selector | Class |
---|---|
cmdk-command | .cmdk-command |
Name | Description |
---|---|
@Input() ariaLabel: string | Accessible Label for this command menu. Not shown visibly. |
@Input() filter?: ((value: string, search: string) => boolean) | Custom filter function for whether each command menu item should matches the given search query. It should return a boolean , false being hidden entirely. You can pass null to disable default filtering. Default: (value, search) => value.toLowerCase().includes(search.toLowerCase()) |
@Input() value?: string | Optional controlled state of the selected command menu item. |
@Input() loading?: boolean | Optional indicator to show loader |
@Input() loop?: boolean | Optionally set to true to turn on looping around when using the arrow keys. |
@Output() valueChanged: EventEmitter<string> | Event handler called when the selected item of the menu changes. |
Render this to show the command input.
Selector | Class |
---|---|
input[cmdkinput] | .cmdk-input |
Name | Description |
---|---|
@Input() updateOn: 'blur' | 'change' | 'input' | Optional indicator to provide event listener when filtering should happen. Default: input |
Contains items and groups.
Selector | Class |
---|---|
cmdk-list | .cmdk-list |
Animate height using the --cmdk-list-height
CSS variable.
1.cmdk-list { 2 min-height: 300px; 3 height: var(--cmdk-list-height); 4 max-height: 500px; 5 transition: height 100ms ease; 6}
To scroll item into view earlier near the edges of the viewport, use scroll-padding:
1.cmdk-list { 2 scroll-padding-block-start: 8px; 3 scroll-padding-block-end: 8px; 4}
Name | Description |
---|---|
@Input() ariaLabel?: string | Accessible Label for this command menu. Not shown visibly. |
Item that becomes active on pointer enter. You should provide a unique value
for each item, but it will be automatically inferred from the .textContent
.
Items will not unmount from the DOM, rather the cmdk-hidden
attribute is applied to hide it from view. This may be relevant in your styling.
State | Selector | Class |
---|---|---|
Default | [cmdkItem] | .cmdk-item |
Active | [cmdkItem][aria-selected] | .cmdk-item-active |
Filtered | [cmdkItem] | .cmdk-item-filtered |
Disabled | [cmdkItem] | .cmdk-item-disabled |
Hidden (not-filtered) | [cmdkItem][cmdk-hidden] | `` |
Name | Description |
---|---|
value: string | undefined; | Contextual Value of the list-item |
@Input() disabled: boolean | Contextually mark the item as disabled. Keyboard navigation will skip this item. |
@Input() filtered: boolean | Contextually mark the item as filtered. |
@Output() selected: EventEmitter<void> | Event handler called when the item is selected |
Groups items together with the given label (.cmdk-group-label
).
Selector | Class |
---|---|
cmdk-group | .cmdk-group |
Groups will not unmount from the DOM, rather the cmdk-hidden
attribute is applied to hide it from view. This may be relevant in your styling.
Name | Description |
---|---|
@Input() label: Content | Label for this command group. Can be HTML string |
@Input() ariaLabel?: string | Accessible Label for this command menu. Not shown visibly. |
Automatically renders when there are no results for the search query.
Selector | Class |
---|---|
*cmdkEmpty | .cmdk-empty |
This will be conditionally renderer when you pass loading=true
with cmdk-command
Selector | Class |
---|---|
*cmdkLoader | .cmdk-loader |
Code snippets for common use cases.
Often selecting one item should navigate deeper, with a more refined set of items. For example selecting "Change theme…" should show new items "Dark theme" and "Light theme". We call these sets of items "pages", and they can be implemented with simple state:
1<cmdk-command (keydown)="onKeyDown($event)"> 2 <input cmdkInput (input)="setSearch($event)" /> 3 <ng-container *ngIf="!page"> 4 <button cmdkItem (selected)="setPages('projects')">Search projects...</button> 5 <button cmdkItem (selected)="setPages('teams')">Join a team...</button> 6 </ng-container> 7 <ng-container *ngIf="page === 'projects'"> 8 <button cmdkItem>Project A</button> 9 <button cmdkItem>Project B</button> 10 </ng-container> 11 <ng-container *ngIf="page === 'teams'"> 12 <button cmdkItem>Team 1</button> 13 <button cmdkItem>Team 2</button> 14 </ng-container> 15</cmdk-command>
1pages: Array<string> = []; 2search = ''; 3 4get page() { 5 return this.pages[this.pages.length - 1]; 6} 7 8onKeyDown(e: KeyboardEvent) { 9 // Escape goes to previous page 10 // Backspace goes to previous page when search is empty 11 if (e.key === 'Escape' || (e.key === 'Backspace' && !this.search)) { 12 e.preventDefault(); 13 this.pages = this.pages.slice(0, -1); 14 } 15} 16 17setSearch(ev: Event) { 18 this.search = (ev.target as HTMLInputElement)?.value; 19} 20 21setPages(page: string) { 22 this.pages.push(page); 23}
Render the items as they become available. Filtering and sorting will happen automatically.
1<cmdk-command [loading]="loading"> 2 <input cmdkInput /> 3 <div *cmdkLoader>Fetching words...</div> 4 <button cmdkItem *ngFor="let item of items" [value]="item"> 5 {{item}} 6 </button> 7</cmdk-command>
1loading = false; 2 3getItems() { 4 this.loading = true; 5 setTimeout(() => { 6 this.items = ['A', 'B', 'C', 'D']; 7 this.loading = false; 8 }, 3000); 9}
We recommend using the Angular CDK Overlay. @ngxpert/cdk relies on the Angular CDK, so this will reduce your bundle size a bit due to shared dependencies.
First, configure the trigger component:
1<button (click)="isDialogOpen = !isDialogOpen" cdkOverlayOrigin #trigger="cdkOverlayOrigin" [attr.aria-expanded]="isDialogOpen"> 2 Actions 3 <kbd>⌘</kbd> 4 <kbd>K</kbd> 5</button> 6<ng-template 7 cdkConnectedOverlay 8 [cdkConnectedOverlayOrigin]="trigger" 9 [cdkConnectedOverlayOpen]="isDialogOpen" 10> 11 <app-sub-command-dialog [value]="value"></app-sub-command-dialog> 12</ng-template>
1isDialogOpen = false; 2 3listener(e: KeyboardEvent) { 4 if (e.key === 'k' && (e.metaKey || e.altKey)) { 5 e.preventDefault(); 6 if (this.isDialogOpen) { 7 this.isDialogOpen = false; 8 } else { 9 this.isDialogOpen = true; 10 } 11 } 12} 13 14ngOnInit() { 15 document.addEventListener('keydown', (ev) => this.listener(ev)); 16} 17 18ngOnDestroy() { 19 document.removeEventListener('keydown', (ev) => this.listener(ev)); 20}
Then, render the cmdk-command
inside CDK Overlay content:
1<div class="cmdk-submenu"> 2 <cmdk-command> 3 <cmdk-list> 4 <cmdk-group [label]="value"> 5 <button cmdkItem *ngFor="let item of items" [value]="item.label"> 6 {{ item.label }} 7 </button> 8 </cmdk-group> 9 </cmdk-list> 10 <input cmdkInput #input placeholder="Search for actions..." /> 11 </cmdk-command> 12</div>
1readonly items: Array<{ label: string }> = [ 2 { 3 label: 'Open Application', 4 }, 5 { 6 label: 'Show in Finder', 7 }, 8 { 9 label: 'Show Info in Finder', 10 }, 11 { 12 label: 'Add to Favorites', 13 }, 14]; 15 16ngAfterViewInit() { 17 this.input.nativeElement.focus(); 18}
You can find global stylesheets to drop in as a starting point for styling. See ngxpert/cmdk/styles for examples.
You can include the SCSS
stylesheet in your application's style file:
1// Global is needed for any theme 2@use "~@ngxpert/cmdk/styles/scss/globals"; 3 4// Then add theme 5@use "~@ngxpert/cmdk/styles/scss/framer"; 6// @use "~@ngxpert/cmdk/styles/scss/vercel"; 7// @use "~@ngxpert/cmdk/styles/scss/linear"; 8// @use "~@ngxpert/cmdk/styles/scss/raycast";
or, use pre-built CSS
file in angular.json
1// ... 2"styles": [ 3 "...", 4 "node_modules/@ngxpert/cmdk/styles/globals.css" 5 "node_modules/@ngxpert/cmdk/styles/framer.css" 6], 7// ...
Accessible? Yes. Labeling, aria attributes, and DOM ordering tested with Voice Over and Chrome DevTools.
Virtualization? No. Good performance up to 2,000-3,000 items, though. Read below to bring your own.
Filter/sort items manually? Yes. Pass filter={yourFilter}
to Command. Better memory usage and performance. Bring your own virtualization this way.
Unstyled? Yes, use the listed CSS selectors.
Weird/wrong behavior? Make sure your [cdkItem]
has a unique value
.
Listen for ⌘K automatically? No, do it yourself to have full control over keybind context.
Thanks goes to these wonderful people (emoji key):
Dharmen Shah ️️️️♿️ 💻 🖋 🎨 📖 💡 🤔 🚧 📦 📆 🔬 | Netanel Basal 💬 💼 🔍 🤔 🚧 🧑🏫 📆 🔬 👀 | Paco 🎨 📖 🤔 🔬 |
This project follows the all-contributors specification. Contributions of any kind welcome!
No vulnerabilities found.
No security vulnerabilities found.