Command Palette
A ⌘K command menu with fuzzy matching, nested pages, grouped results and full keyboard control.
Overview
A quiet, precise command menu in the tradition of good desktop software. Results are matched with a subsequence fuzzy scorer that rewards word starts and consecutive letters, and the matched characters are underlined so people can see why something matched.
Items can open nested pages, such as Theme → Dark. A breadcrumb shows where you are, and Backspace on an empty query takes you back. The list height animates as results change, so the panel never jumps.
It is built on a native <dialog> with a proper ARIA combobox and listbox. Focus is trapped and restored by the browser, Escape steps back a page before closing, and ⌘K or Ctrl+K toggles it from anywhere.
When to use
- Moving between a handful of destinations or sections.
When not to use
- Large or deep hierarchies: use a sidebar or search.
Installation
pnpm dlx shadcn@latest add https://sundaeui.dev/r/command-palette.jsonnpx shadcn@latest add https://sundaeui.dev/r/command-palette.jsonyarn dlx shadcn@latest add https://sundaeui.dev/r/command-palette.jsonbunx --bun shadcn@latest add https://sundaeui.dev/r/command-palette.jsonWorks in any React project. Files are written to components/sundae/. Read the source in the stage’s Code view. Pro assets need a personal token. See authenticated installs.
- No dependencies beyond React 18 or later.
- Copy command-palette.tsx and command-palette.css into components/sundae/.
- The CSS file is imported by the component, so no extra configuration is needed.
- Import the component and use it as shown below.
Usage
import { CommandPalette } from '@/components/sundae/command-palette'
export function AppCommands() {
const router = useRouter()
return (
<CommandPalette
items={[
{ id: 'new', group: 'Create', label: 'New document', shortcut: ['⌘', 'N'], onSelect: createDoc },
{ id: 'billing', group: 'Navigate', label: 'Go to billing', keywords: ['invoice'], onSelect: () => router.push('/billing') },
{
id: 'theme', group: 'Preferences', label: 'Change theme',
children: [
{ id: 'light', label: 'Light', onSelect: () => setTheme('light') },
{ id: 'dark', label: 'Dark', onSelect: () => setTheme('dark') },
],
},
]}
/>
)
}Props
| Prop | Type | Default |
|---|---|---|
items*Commands: { id, label, group?, keywords?, icon?, shortcut?, children?, onSelect? }. | CommandItem[] | — |
openControlled open state. | boolean | — |
onOpenChangeCalled when the palette opens or closes. | (open: boolean) => void | — |
hotkeyToggle with ⌘K or Ctrl+K. | boolean | true |
placeholderInput placeholder on the root page. | string | 'Type a command or search…' |
inlineRender the panel inline instead of as a modal, for embedding or settings pages. | boolean | — |
emptyMessageShown when nothing matches. | string | 'No results.' |
Accessibility
- The input is a role="combobox" that controls a role="listbox". The active option is announced via aria-activedescendant while focus stays in the input.
- Uses a native <dialog> opened with showModal(), so focus trapping, the inert background and focus restoration come from the browser.
- Escape steps back one nested page before closing the dialog.
- Emacs-style Ctrl+N and Ctrl+P work alongside the arrow keys.
Customization
- The palette is monochrome by default. Set --cmdk-accent to your brand colour for the active marker.
- fuzzy() is exported if you want the same ranking elsewhere, for example in a sidebar filter.
- For large or remote result sets, filter on the server and pass the results as items. Scoring is skipped when the query is empty.
FAQ
- Is Command Palette free?
- Command Palette is part of Sundae Pro. The live preview is open to everyone; the source installs with a Pro account.
- How do I install Command Palette?
- Run npx shadcn@latest add https://sundaeui.dev/r/command-palette.json. The files are written to components/sundae/. It has no dependencies beyond React 18 or later.
- Does Command Palette support keyboard and screen readers?
- The input is a role="combobox" that controls a role="listbox". The active option is announced via aria-activedescendant while focus stays in the input. Uses a native <dialog> opened with showModal(), so focus trapping, the inert background and focus restoration come from the browser.
- Does Command Palette respect reduced motion?
- Yes. With prefers-reduced-motion set, decorative motion is removed and state changes are instant.
More like this
All navigation →Dropdown Menu
Navigation · Understated- Pro
Collapsible Sidebar
Navigation · Refined - Pro
Nav Dropdown
Navigation · Refined Taffy Tabs
Navigation · PlayfulSegmented Control
Navigation · RefinedPagination
Navigation · Refined