Skip to Content
PatternsNavigationCommand Palette

Command Palette

A keyboard-driven command menu (⌘K style) for quick navigation and actions. Built using Box, Column, Row, Icon, Text, and TextField components.

or press ⌘K

Source

'use client'; import { Search } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Box, Column, Icon, Row, Text, TextField } from '@umami/react-zen'; export function CommandPalette({ groups, isOpen, onClose, placeholder = 'Search commands...', }) { const [search, setSearch] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); const filteredGroups = groups .map((group) => ({ ...group, items: group.items.filter( (item) => item.label.toLowerCase().includes(search.toLowerCase()) || item.description?.toLowerCase().includes(search.toLowerCase()) ), })) .filter((group) => group.items.length > 0); const allItems = filteredGroups.flatMap((group) => group.items); useEffect(() => { if (!isOpen) { setSearch(''); setSelectedIndex(0); } }, [isOpen]); useEffect(() => { const handleKeyDown = (e) => { if (!isOpen) return; if (e.key === 'Escape') { onClose(); } else if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex((i) => (i + 1) % allItems.length); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex((i) => (i - 1 + allItems.length) % allItems.length); } else if (e.key === 'Enter' && allItems[selectedIndex]) { allItems[selectedIndex].onSelect(); onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose, allItems, selectedIndex]); if (!isOpen) return null; let itemIndex = -1; return ( <Column position="fixed" style={{ inset: 0, zIndex: 50 }} alignItems="center" paddingTop="20"> <Box position="absolute" style={{ inset: 0 }} backgroundColor="overlay" onClick={onClose} /> <Box position="relative" width="100%" maxWidth="32rem" backgroundColor="surface-raised" borderRadius="lg" border borderColor="muted" overflow="hidden"> <Row padding="3" gap="3" alignItems="center" borderColor="muted" border="bottom"> <Icon color="muted"><Search /></Icon> <TextField value={search} onChange={setSearch} placeholder={placeholder} autoFocus style={{ border: 'none', background: 'transparent', outline: 'none', flex: 1 }} /> </Row> <Column maxHeight="20rem" overflow="auto"> {filteredGroups.length === 0 ? ( <Box padding="4" textAlign="center"><Text color="muted">No results found</Text></Box> ) : ( filteredGroups.map((group) => ( <Column key={group.label}> <Box paddingX="3" paddingY="2"> <Text color="muted" weight="semibold">{group.label}</Text> </Box> {group.items.map((item) => { itemIndex++; const isSelected = itemIndex === selectedIndex; return ( <Row key={item.id} paddingX="3" paddingY="2" gap="3" alignItems="center" backgroundColor={isSelected ? 'interactive' : undefined} className="cursor-pointer" onClick={() => { item.onSelect(); onClose(); }}> {item.icon && <Icon size="sm" color="muted">{item.icon}</Icon>} <Column flexGrow="1" gap="0"> <Text>{item.label}</Text> {item.description && <Text color="muted">{item.description}</Text>} </Column> {item.shortcut && ( <Row gap="1"> {item.shortcut.split('+').map((key, i) => ( <Box key={i} paddingX="2" paddingY="1" backgroundColor="surface-base" borderRadius="sm" border borderColor="muted"> <Text color="muted">{key}</Text> </Box> ))} </Row> )} </Row> ); })} </Column> )) )} </Column> </Box> </Column> ); } export function useCommandPalette() { const [isOpen, setIsOpen] = useState(false); useEffect(() => { const handleKeyDown = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setIsOpen((open) => !open); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, []); return { isOpen, open: () => setIsOpen(true), close: () => setIsOpen(false), toggle: () => setIsOpen((open) => !open), }; }

Features

  • Keyboard navigation: Arrow keys to navigate, Enter to select, Escape to close
  • Global shortcut: Press ⌘K (or Ctrl+K) to toggle
  • Search filtering: Type to filter commands by label or description
  • Grouped commands: Organize commands into logical groups
  • Keyboard shortcuts: Display shortcuts for each command

Variations

With descriptions

Quick Actions
Create new projectStart a new project from scratch
Import from GitHubImport an existing repository
Browse templatesStart with a pre-built template