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

cmdk-wc

v1.1.4

Published

Command menu web component built with Lit

Readme

cmdk-wc

A framework-agnostic command palette web component. Built as native Web Components with full TypeScript support, works in React, Vue, Svelte, Angular, and vanilla JavaScript. Inspired by cmdk, reimagined for any framework.

npm version npm downloads MIT License Built with Lit Published on webcomponents.org


Demo


Table of Contents


Features

  • 🎯 Native Web Components — Works across any framework or vanilla JS
  • ⌨️ Keyboard Navigation — Full Cmd+K support, arrow keys, enter to select, escape to close with built-in visual keyboard shortcuts footer
  • 🔄 Multi-Page Navigation — Drill-down into categories with breadcrumb support
  • 🎨 Themeable — 14 CSS custom properties for complete visual customization, built-in dark mode
  • 📦 Lightweight — ~15KB minified, zero external dependencies beyond Lit
  • 🧩 Accessible — ARIA roles, screen reader support, semantic HTML, keyboard shortcuts footer with aria-labels
  • TypeScript — Full type definitions included for all components and interfaces
  • 🚀 Framework Agnostic — Drop into React, Vue, Svelte, Angular, or any framework

About cmdk-wc

cmdk-wc is a framework-agnostic command palette web component that works with any JavaScript framework or vanilla JavaScript.

This component follows Web Components Standards (Custom Elements, Shadow DOM, Slot API) and is compatible with all modern browsers.


Installation

npm install cmdk-wc

Or with your favorite package manager:

pnpm add cmdk-wc
yarn add cmdk-wc
bun add cmdk-wc

Quick Start

Vanilla JavaScript

<!-- 1. Import the component -->
<script type="module">
  import 'cmdk-wc';
</script>

<!-- 2. Use it in HTML -->
<cmdk-palette id="palette"></cmdk-palette>

<!-- 3. Provide data via JavaScript -->
<script>
  const palette = document.getElementById('palette');

  palette.pages = [
    {
      id: 'root',
      lists: [
        {
          id: 'actions',
          heading: 'Actions',
          items: [
            { id: 'new', label: 'New File', icon: '✨' },
            { id: 'settings', label: 'Settings', icon: '⚙️' },
          ],
        },
      ],
    },
  ];

  palette.addEventListener('cmdk-select', (event) => {
    console.log('Selected:', event.detail);
  });

  // Open palette
  palette.open = true;
</script>

React

import { useRef, useEffect, useState } from 'react';
import 'cmdk-wc';

export default function App() {
  const paletteRef = useRef<HTMLElement>(null);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    if (!paletteRef.current) return;

    paletteRef.current.pages = [
      {
        id: 'root',
        lists: [
          {
            id: 'actions',
            heading: 'Actions',
            items: [
              { id: 'new', label: 'New File', icon: '✨' },
              { id: 'settings', label: 'Settings', icon: '⚙️' },
            ],
          },
        ],
      },
    ];
  }, []);

  useEffect(() => {
    if (paletteRef.current) {
      paletteRef.current.open = open;
    }
  }, [open]);

  useEffect(() => {
    const handleSelect = (event: Event) => {
      const customEvent = event as CustomEvent;
      console.log('Selected:', customEvent.detail);
    };

    paletteRef.current?.addEventListener('cmdk-select', handleSelect);
    return () =>
      paletteRef.current?.removeEventListener('cmdk-select', handleSelect);
  }, []);

  return (
    <div>
      <button onClick={() => setOpen(true)}>Open Palette</button>
      <cmdk-palette ref={paletteRef}></cmdk-palette>
    </div>
  );
}

Note: See React Integration Guide for TypeScript setup and detailed patterns.

Vue 3

<template>
  <div>
    <button
      @click="palette?.open ? (palette.open = false) : (palette.open = true)"
    >
      Open Palette
    </button>
    <cmdk-palette ref="paletteRef"></cmdk-palette>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import 'cmdk-wc';

const paletteRef = ref<HTMLElement>();

onMounted(() => {
  if (!paletteRef.value) return;

  paletteRef.value.pages = [
    {
      id: 'root',
      lists: [
        {
          id: 'actions',
          heading: 'Actions',
          items: [
            { id: 'new', label: 'New File', icon: '✨' },
            { id: 'settings', label: 'Settings', icon: '⚙️' },
          ],
        },
      ],
    },
  ];

  paletteRef.value.addEventListener('cmdk-select', (event: Event) => {
    const customEvent = event as CustomEvent;
    console.log('Selected:', customEvent.detail);
  });
});
</script>

Svelte

<script lang="ts">
  import 'cmdk-wc';
  import { onMount } from 'svelte';

  let paletteRef: HTMLElement;
  let open = false;

  onMount(() => {
    if (!paletteRef) return;

    paletteRef.pages = [
      {
        id: 'root',
        lists: [
          {
            id: 'actions',
            heading: 'Actions',
            items: [
              { id: 'new', label: 'New File', icon: '✨' },
              { id: 'settings', label: 'Settings', icon: '⚙️' },
            ]
          }
        ]
      }
    ];

    paletteRef.addEventListener('cmdk-select', (event) => {
      console.log('Selected:', event.detail);
    });
  });

  $: {
    if (paletteRef) {
      paletteRef.open = open;
    }
  }
</script>

<button on:click={() => (open = !open)}>
  Open Palette
</button>
<cmdk-palette bind:this={paletteRef}></cmdk-palette>

Angular

// app.component.ts
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import 'cmdk-wc';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="togglePalette()">Open Palette</button>
    <cmdk-palette #palette></cmdk-palette>
  `,
})
export class AppComponent implements AfterViewInit {
  @ViewChild('palette') paletteRef!: ElementRef<HTMLElement>;

  ngAfterViewInit() {
    const palette = this.paletteRef.nativeElement;

    palette.pages = [
      {
        id: 'root',
        lists: [
          {
            id: 'actions',
            heading: 'Actions',
            items: [
              { id: 'new', label: 'New File', icon: '✨' },
              { id: 'settings', label: 'Settings', icon: '⚙️' },
            ],
          },
        ],
      },
    ];

    palette.addEventListener('cmdk-select', (event: Event) => {
      const customEvent = event as CustomEvent;
      console.log('Selected:', customEvent.detail);
    });
  }

  togglePalette() {
    this.paletteRef.nativeElement.open = !this.paletteRef.nativeElement.open;
  }
}

Component API

<cmdk-palette>

The root component that orchestrates state, keyboard navigation, and data filtering.

Properties

| Property | Type | Default | Description | | -------- | ---------------- | -------- | ------------------------------------------------- | | pages | CmdkPageData[] | [] | Array of page objects containing groups and items | | open | boolean | false | Whether the palette is visible | | page | string | 'root' | Current active page ID for drill-down navigation |

Methods

| Method | Signature | Description | | -------------- | ------------ | --------------------------------------- | | focusInput() | () => void | Programmatically focus the search input |

Events

| Event | Detail | Fired When | | --------------- | ---------------------------------------------- | ----------------------------------------- | | cmdk-select | { id: string; label: string; href?: string } | User selects a leaf item (non-navigation) | | cmdk-navigate | { pageId: string } | User navigates to a new page | | cmdk-open | {} | Palette opens | | cmdk-close | {} | Palette closes |

Example

const palette = document.querySelector('cmdk-palette');

// Set data
palette.pages = [{ id: 'root', lists: [...] }];

// Open/close
palette.open = true;

// Listen for events
palette.addEventListener('cmdk-select', (event) => {
  console.log('Item selected:', event.detail.label);
});

<cmdk-input>

The search input field. Automatically rendered inside <cmdk-palette>. Handles text input and displays breadcrumb navigation on multi-page palettes.

Properties

| Property | Type | Default | Description | | ------------- | -------- | ---------------------- | ---------------------- | | placeholder | string | 'Search commands...' | Input placeholder text |

Methods

| Method | Signature | Description | | --------- | ------------ | ----------------------- | | focus() | () => void | Focus the input element | | blur() | () => void | Blur the input element |

Events

| Event | Detail | Fired When | | ------------ | ------------------- | ------------------------------ | | cmdk-input | { value: string } | User types in the search field |


<cmdk-list>

Container for groups and free-search actions. Automatically rendered inside <cmdk-palette>. Handles scrolling and ARIA roles for accessibility.

Properties

| Property | Type | Default | Description | | -------- | ---- | ------- | -------------------- | | (none) | - | - | No public properties |


<cmdk-group>

Groups related items under a heading label. Automatically rendered for each list in the active page.

Properties

| Property | Type | Default | Description | | -------- | -------- | ------- | ------------------ | | label | string | '' | Group heading text |

Slots

| Slot | Content | | --------- | ---------------------- | | (default) | <cmdk-item> children |


<cmdk-item>

A single command or action row. Displays an icon (optional), label, and type hint (Link/Action/navigation).

Properties

| Property | Type | Default | Description | | ---------- | -------------- | ------- | ----------------------------------------------------------------- | | itemData | CmdkItemData | {} | The full item object (id, label, icon, href, page, onClick, etc.) | | selected | boolean | false | Whether this item is currently highlighted |

Events

| Event | Detail | Fired When | | ----------------- | -------------- | ---------------------------------------- | | cmdk-item-click | CmdkItemData | User clicks or presses Enter on the item |

Example

interface CmdkItemData {
  id: string;
  label: string;
  icon?: string; // Icon name or inline SVG
  href?: string; // If set, type hint shows "Link"
  page?: string; // If set, drill down to this page
  closeOnSelect?: boolean; // Default: true
  onClick?: () => void; // Optional side-effect
}

<cmdk-free-search-action>

Fallback action shown when search yields no results. Allows users to perform a custom action with their search query.

Properties

| Property | Type | Default | Description | | -------- | -------- | ------- | ----------------------------- | | query | string | '' | The current search query text |

Example

<cmdk-palette id="palette"></cmdk-palette>

<script>
  const palette = document.getElementById('palette');

  palette.pages = [
    {
      id: 'root',
      lists: [
        {
          id: 'actions',
          heading: 'Actions',
          items: [{ id: 'new', label: 'New', icon: '✨' }],
        },
      ],
    },
  ];

  palette.addEventListener('cmdk-select', (event) => {
    if (event.detail.id === 'free-search') {
      console.log('User searched for:', event.detail.query);
    }
  });
</script>

Data Structure

All data is provided as a nested structure of pages, groups, and items:

interface CmdkPageData {
  id: string; // Unique page identifier
  lists: CmdkListData[]; // Array of groups to display
}

interface CmdkListData {
  id: string; // Unique group identifier
  heading: string; // Label shown above items
  items: CmdkItemData[]; // Array of commands
}

interface CmdkItemData {
  id: string; // Unique item identifier
  label: string; // Display text
  icon?: string; // Icon name or inline SVG
  href?: string; // Optional: if set, item is a link
  page?: string; // Optional: navigate to page with this id
  closeOnSelect?: boolean; // Default: true; set false for navigation items
  onClick?: () => void; // Optional: side-effect on click
}

Example:

const data = [
  {
    id: 'root',
    lists: [
      {
        id: 'navigation',
        heading: 'Navigation',
        items: [
          { id: 'home', label: 'Home', icon: '🏠', href: '/' },
          { id: 'projects', label: 'Projects', icon: '📁', page: 'projects' },
        ],
      },
      {
        id: 'actions',
        heading: 'Actions',
        items: [
          {
            id: 'new',
            label: 'New File',
            icon: '✨',
            onClick: () => createFile(),
          },
        ],
      },
    ],
  },
  {
    id: 'projects',
    lists: [
      {
        id: 'recent',
        heading: 'Recent Projects',
        items: [
          { id: 'proj-1', label: 'My App', icon: '💻' },
          { id: 'proj-2', label: 'Website', icon: '🌐' },
        ],
      },
    ],
  },
];

Theming

All visual styling is driven by CSS custom properties. Customize the palette's appearance by setting properties on the cmdk-palette element or :root.

CSS Custom Properties

| Property | Light Default | Dark Default | Description | | ------------------------ | ----------------- | ----------------- | ----------------------------- | | --cmdk-accent | #3b82f6 | #60a5fa | Primary accent color | | --cmdk-accent-hover | #2563eb | #3b82f6 | Hover state for accent | | --cmdk-text | #0f172a | #f1f5f9 | Primary text color | | --cmdk-muted | #64748b | #94a3b8 | Secondary/muted text | | --cmdk-border | #e2e8f0 | #334155 | Border color | | --cmdk-surface | #f8fafc | #1e293b | Item hover background | | --cmdk-bg | #ffffff | #0f172a | Primary background | | --cmdk-overlay | rgba(0,0,0,0.4) | rgba(0,0,0,0.6) | Backdrop overlay | | --cmdk-radius | 8px | 8px | Border radius | | --cmdk-list-max-height | 400px | 400px | Max height of scrollable list |

Light Theme Example

cmdk-palette {
  --cmdk-accent: #3b82f6;
  --cmdk-accent-hover: #2563eb;
  --cmdk-text: #0f172a;
  --cmdk-muted: #64748b;
  --cmdk-border: #e2e8f0;
  --cmdk-surface: #f8fafc;
  --cmdk-bg: #ffffff;
  --cmdk-overlay: rgba(0, 0, 0, 0.4);
  --cmdk-radius: 8px;
  --cmdk-list-max-height: 400px;
}

Dark Theme Example

@media (prefers-color-scheme: dark) {
  cmdk-palette {
    --cmdk-accent: #60a5fa;
    --cmdk-accent-hover: #3b82f6;
    --cmdk-text: #f1f5f9;
    --cmdk-muted: #94a3b8;
    --cmdk-border: #334155;
    --cmdk-surface: #1e293b;
    --cmdk-bg: #0f172a;
    --cmdk-overlay: rgba(0, 0, 0, 0.6);
    --cmdk-radius: 8px;
    --cmdk-list-max-height: 400px;
  }
}

Custom Theme (Purple)

cmdk-palette {
  --cmdk-accent: #8b5cf6;
  --cmdk-accent-hover: #7c3aed;
  --cmdk-bg: #fef5ff;
  --cmdk-text: #3f0f5c;
  --cmdk-surface: #f3e8ff;
  --cmdk-border: #e9d5ff;
}

@media (prefers-color-scheme: dark) {
  cmdk-palette {
    --cmdk-accent: #d8b4fe;
    --cmdk-accent-hover: #f0abfc;
    --cmdk-bg: #2d0a4e;
    --cmdk-text: #f3e8ff;
    --cmdk-surface: #4c1d95;
    --cmdk-border: #6b21a8;
  }
}

For complete theming guidance, see the Theming Guide.


Keyboard Shortcuts

| Key | Behavior | | ---------------------------------- | ------------------------------------ | | Cmd/Ctrl + K | Toggle palette open/closed (global) | | / | Navigate items up/down | | Enter | Select highlighted item | | Esc | Go back a page or close palette | | Backspace | Clear search (when input is focused) |


TypeScript Support

Full TypeScript definitions are included. Import types from the package to ensure type safety:

import type { CmdkPageData, CmdkListData, CmdkItemData } from 'cmdk-wc';

const pages: CmdkPageData[] = [
  {
    id: 'root',
    lists: [
      {
        id: 'actions',
        heading: 'Actions',
        items: [
          {
            id: 'new',
            label: 'New File',
            icon: '✨',
            onClick: () => console.log('Creating new file'),
          },
        ],
      },
    ],
  },
];

Framework-Specific Setup

  • React: See React Integration for type declaration setup
  • Vue: Native support, full IntelliSense in templates
  • Svelte: Native support, full IntelliSense in templates
  • Angular: Use CUSTOM_ELEMENTS_SCHEMA to avoid template errors

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • All modern mobile browsers

Web Components are widely supported. Check caniuse.com for detailed compatibility.


Advanced Usage

Multi-Page Navigation

Create drill-down experiences by providing multiple pages and using the page property on items:

const palette = document.querySelector('cmdk-palette');

palette.pages = [
  {
    id: 'root',
    lists: [
      {
        heading: 'Pages',
        items: [{ id: 'go-projects', label: 'Projects', page: 'projects' }],
      },
    ],
  },
  {
    id: 'projects',
    lists: [
      { heading: 'Your Projects', items: [{ id: 'p1', label: 'My App' }] },
    ],
  },
];

palette.open = true;

Custom Actions with onClick

Execute custom side-effects when items are selected:

palette.pages = [
  {
    id: 'root',
    lists: [
      {
        heading: 'Actions',
        items: [
          {
            id: 'create',
            label: 'Create New File',
            onClick: () => {
              fs.writeFile('newfile.txt', '', () => {
                console.log('File created!');
              });
            },
          },
        ],
      },
    ],
  },
];

Listening to Events

palette.addEventListener('cmdk-select', (event) => {
  console.log('Selected:', event.detail);
  // { id: 'new', label: 'New File', href?: '...' }
});

palette.addEventListener('cmdk-navigate', (event) => {
  console.log('Navigated to page:', event.detail.pageId);
});

palette.addEventListener('cmdk-open', () => console.log('Opened'));
palette.addEventListener('cmdk-close', () => console.log('Closed'));

Framework Guides

Detailed integration guides for each framework:


Contributing

This repository is currently not open for contributions.
Please open an issue to discuss before submitting any pull requests.


License

MIT License (Open Source Initiative Approved)

This software is released under the MIT License, which permits commercial use, modification, and distribution.

You are free to:

  • ✅ Use this software for any purpose (personal, educational, commercial)
  • ✅ Modify and distribute modified versions
  • ✅ Include this software in proprietary applications

Conditions:

  • Include a copy of the License and Copyright notice with distributions

See the full LICENSE file for details.


Support & Issues


Building from Source

Prerequisites

  • Node.js 18+
  • pnpm (or npm/yarn/bun)

Setup

git clone https://github.com/AhmedAshraf-GPT/cmdk.git
cd cmdk
pnpm install

Development

# Start Vite dev server with hot reload
pnpm dev

# Build library
pnpm build

# Run tests (if applicable)
pnpm test

# Lint code
pnpm lint

Project Structure

src/
├── components/          # Web component definitions
│   ├── cmdkpalette.ts   # Main palette orchestrator
│   ├── cmdkinput.ts     # Search input
│   ├── cmdklist.ts      # Item list container
│   ├── cmdkgroup.ts     # Item group
│   ├── cmdkitem.ts      # Individual item
│   └── ...
├── index.ts            # Entry point
└── types.ts            # TypeScript definitions

types/
├── index.d.ts          # Main type definitions
├── react.d.ts          # React integration types
├── vue.d.ts            # Vue integration types
└── ...                 # Other framework types

docs/
├── react.md            # React integration guide
├── vue.md              # Vue 3 integration guide
├── svelte.md           # Svelte integration guide
├── angular.md          # Angular integration guide
├── vanilla.md          # Vanilla JS guide
└── theming.md          # CSS customization guide

Maintenance & Updates

This project is actively maintained. For version history and changelog, see GitHub Releases.

Current Version

v1.1.0 - Latest release with full keyboard navigation, accessibility features, and framework integrations.


Inspiration

Inspired by cmdk by Paco Coursey, reimagined as a framework-agnostic Web Component.