@liaskos/command-palette
v1.0.1
Published
A beautiful, accessible command palette component for React applications - inspired by VS Code and Linear
Maintainers
Readme
@liaskos/command-palette
A beautiful, accessible command palette component for React applications, inspired by VS Code and Linear. Built with TypeScript and designed to match the v1 prototype exactly.
Features
- 🎨 Beautiful Design - Clean, modern interface with HSL-based theming
- ⌨️ Keyboard Navigation - Full keyboard support with arrow keys and shortcuts
- 🔍 Fuzzy Search - Intelligent search with keyword matching
- 🎯 Accessibility - ARIA compliant with screen reader support
- 📱 Responsive - Works on all screen sizes
- 🎪 Zero Dependencies - Only requires React and Lucide React icons
- 🏗️ TypeScript - Fully typed with comprehensive interfaces
- 🎭 Customizable - Flexible theming and configuration options
Installation
npm install @liaskos/command-palette
# or
yarn add @liaskos/command-palette
# or
pnpm add @liaskos/command-paletteQuick Start
import React, { useState } from 'react';
import { CommandPalette, CommandPaletteTrigger, useGlobalShortcuts } from '@liaskos/command-palette';
import { Home, Folder, CheckSquare, Users } from 'lucide-react';
const actions = [
{
id: 'home',
label: 'Go to Home',
description: 'Navigate to the home page',
icon: Home,
keywords: ['home', 'dashboard'],
group: 'Navigation',
},
{
id: 'projects',
label: 'View Projects',
description: 'See all your projects',
icon: Folder,
keywords: ['projects', 'work'],
group: 'Navigation',
},
// ... more actions
];
function App() {
const [open, setOpen] = useState(false);
// Enable global keyboard shortcut (⌘K / Ctrl+K)
useGlobalShortcuts(() => setOpen(true));
const handleSelect = (action) => {
console.log('Selected:', action);
// Handle action selection
};
return (
<div>
{/* Trigger Button */}
<CommandPaletteTrigger onClick={() => setOpen(true)} />
{/* Command Palette */}
<CommandPalette
open={open}
actions={actions}
onSelect={handleSelect}
onClose={() => setOpen(false)}
/>
</div>
);
}Advanced Usage
Custom Actions
import { Plus, Calendar, Settings } from 'lucide-react';
const customActions = [
{
id: 'create-project',
label: 'Create New Project',
description: 'Start a new project',
icon: Plus,
keywords: ['create', 'new', 'project'],
group: 'Quick Actions',
shortcut: '⌘N',
data: { type: 'project' },
},
{
id: 'schedule-meeting',
label: 'Schedule Meeting',
description: 'Create a new meeting',
icon: Calendar,
keywords: ['meeting', 'calendar'],
group: 'Quick Actions',
disabled: false,
},
];Custom Styling
const customStyle = {
maxWidth: '600px',
backgroundColor: 'hsl(0 0% 3%)',
color: 'hsl(0 0% 98%)',
};
<CommandPalette
open={open}
actions={actions}
style={customStyle}
maxHeight={400}
placeholder="Search anything..."
emptyMessage="No commands found."
onSelect={handleSelect}
onClose={() => setOpen(false)}
/>Dynamic Actions
function DynamicCommandPalette() {
const [actions, setActions] = useState(defaultActions);
const handleSelect = (action) => {
switch (action.id) {
case 'create-project':
// Add new project action dynamically
setActions(prev => [...prev, {
id: 'new-project-' + Date.now(),
label: 'New Project',
description: 'Recently created project',
icon: Folder,
group: 'Recent',
}]);
break;
// ... handle other actions
}
};
return (
<CommandPalette
open={open}
actions={actions}
onSelect={handleSelect}
onClose={() => setOpen(false)}
/>
);
}API Reference
CommandPalette Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| open | boolean | false | Whether the command palette is open |
| actions | CommandAction[] | [] | Array of actions to display |
| placeholder | string | "Search projects, tasks..." | Placeholder text for search input |
| emptyMessage | string | "No results found." | Message when no results |
| onSelect | (action: CommandAction) => void | undefined | Callback when action is selected |
| onClose | () => void | undefined | Callback when palette is closed |
| style | React.CSSProperties | undefined | Custom styles |
| className | string | undefined | CSS class name |
| maxHeight | number | 320 | Maximum height of results area |
| fuzzySearch | boolean | true | Enable fuzzy search |
| searchDelay | number | 150 | Search debounce delay in ms |
CommandAction Interface
interface CommandAction {
readonly id: string; // Unique identifier
readonly label: string; // Display name
readonly description?: string; // Optional description
readonly icon?: LucideIcon; // Optional Lucide icon
readonly keywords?: readonly string[]; // Search keywords
readonly group?: string; // Group category
readonly shortcut?: string; // Keyboard shortcut
readonly disabled?: boolean; // Whether action is disabled
readonly data?: Record<string, any>; // Custom data
}Hooks
useGlobalShortcuts
Enables global keyboard shortcuts (⌘K / Ctrl+K) to open the command palette.
import { useGlobalShortcuts } from '@liaskos/command-palette';
function App() {
const [open, setOpen] = useState(false);
useGlobalShortcuts(() => setOpen(true));
return (/* ... */);
}useCommandPalette
Provides state management for the command palette.
import { useCommandPalette } from '@liaskos/command-palette';
const {
open,
query,
selectedIndex,
setQuery,
setSelectedIndex,
openPalette,
closePalette,
selectAction,
} = useCommandPalette(actions, onSelect, onClose);useSearch
Handles search functionality with debouncing and fuzzy matching.
import { useSearch } from '@liaskos/command-palette';
const results = useSearch(actions, query, fuzzySearch, delay);Keyboard Shortcuts
- ⌘K / Ctrl+K - Open command palette
- Escape - Close command palette
- ↑/↓ Arrow Keys - Navigate through results
- Enter - Select highlighted action
Theming
The component uses an HSL-based color system for consistent theming:
const customTheme = {
colors: {
background: 'hsl(0 0% 100%)',
foreground: 'hsl(222.2 84% 4.9%)',
muted: 'hsl(210 40% 96%)',
mutedForeground: 'hsl(215.4 16.3% 46.9%)',
border: 'hsl(214.3 31.8% 91.4%)',
accent: 'hsl(210 40% 96%)',
accentForeground: 'hsl(222.2 84% 4.9%)',
overlay: 'hsla(0, 0%, 0%, 0.5)',
},
// ... more theme options
};Examples
With React Router
import { useNavigate } from 'react-router-dom';
function App() {
const navigate = useNavigate();
const actions = [
{
id: 'dashboard',
label: 'Dashboard',
icon: Home,
data: { route: '/dashboard' },
},
{
id: 'projects',
label: 'Projects',
icon: Folder,
data: { route: '/projects' },
},
];
const handleSelect = (action) => {
if (action.data?.route) {
navigate(action.data.route);
}
};
return (
<CommandPalette
actions={actions}
onSelect={handleSelect}
/>
);
}With Next.js
import { useRouter } from 'next/router';
function App() {
const router = useRouter();
const handleSelect = (action) => {
if (action.data?.route) {
router.push(action.data.route);
}
};
return (
<CommandPalette
actions={actions}
onSelect={handleSelect}
/>
);
}Browser Support
- Chrome/Edge 88+
- Firefox 85+
- Safari 14+
- iOS Safari 14+
- Android Chrome 88+
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please read our contributing guidelines and code of conduct.
May the force be with you - Alexandros Liaskos
