typescriptadvanced
Command Palette Component
Keyboard-driven command palette with fuzzy search, keyboard navigation, and grouping.
typescriptPress ⌘/Ctrl + Shift + C to copy
import { useState, useEffect, useRef, useMemo } from 'react';
interface Command {
id: string;
label: string;
group?: string;
shortcut?: string;
action: () => void;
}
interface Props {
commands: Command[];
isOpen: boolean;
onClose: () => void;
}
export function CommandPalette({ commands, isOpen, onClose }: Props) {
const [query, setQuery] = useState('');
const [selected, setSelected] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const filtered = useMemo(
() => commands.filter(c => c.label.toLowerCase().includes(query.toLowerCase())),
[commands, query]
);
useEffect(() => {
if (isOpen) { setQuery(''); setSelected(0); inputRef.current?.focus(); }
}, [isOpen]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (!isOpen) return;
if (e.key === 'ArrowDown') { e.preventDefault(); setSelected(s => Math.min(s + 1, filtered.length - 1)); }
if (e.key === 'ArrowUp') { e.preventDefault(); setSelected(s => Math.max(s - 1, 0)); }
if (e.key === 'Enter' && filtered[selected]) { filtered[selected].action(); onClose(); }
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [isOpen, filtered, selected, onClose]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 flex items-start justify-center pt-[20vh] z-50" onClick={onClose}>
<div className="bg-[#111] border border-white/10 rounded-xl w-full max-w-lg" onClick={e => e.stopPropagation()}>
<input
ref={inputRef}
value={query}
onChange={e => { setQuery(e.target.value); setSelected(0); }}
placeholder="Type a command..."
className="w-full p-4 bg-transparent border-b border-white/10 outline-none text-white"
/>
<ul className="max-h-64 overflow-y-auto">
{filtered.map((cmd, i) => (
<li
key={cmd.id}
className={`px-4 py-2 cursor-pointer flex justify-between ${i === selected ? 'bg-blue-500/20' : 'hover:bg-white/5'}`}
onClick={() => { cmd.action(); onClose(); }}
>
<span>{cmd.label}</span>
{cmd.shortcut && <kbd className="text-xs text-gray-500">{cmd.shortcut}</kbd>}
</li>
))}
</ul>
</div>
</div>
);
}Use Cases
- App navigation
- Power user features
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptbeginner
useDebounce — Debounced Value Hook
Debounce any rapidly-changing value with a configurable delay. Useful for search inputs and resize handlers.
Best for: Search-as-you-type
#hooks#debounce
typescriptbeginner
useKeyboardShortcut Hook
Register global keyboard shortcuts with modifier keys, preventing defaults, and cleanup on unmount.
Best for: Command palette shortcuts
#react#hooks
typescriptbeginner
Search with Text Highlighting
Build a search component that highlights matching text within results using safe HTML rendering.
Best for: Search results display
#react#search
typescriptintermediate
useStepWizard Hook
Multi-step wizard hook that manages step navigation, validation gates, and completion state.
Best for: Onboarding flows
#hooks#wizard