Skip to content

Command Palette

Find app actions and destinations from a keyboard shortcut, with nested pages and key hints.

import { CommandPalette, CommandPalettePage, CommandPaletteState } from '@youj/react/command'

Installation & usage

npm i @youj/react cmdk

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Requires cmdk, an optional dependency. Import it from @youj/react/command.

Use a command palette to put every action and destination in the app one shortcut away, for people who live on the keyboard. It isn't the only way to reach anything: each command should also have a visible home in the interface. For a searchable list inside a page, a picker or a filter, use Command on its own.

import { CommandGroup, CommandItem, CommandPalette } from '@youj/react/command'

export function AppPalette() {
  return (
    <CommandPalette>
      {({ page, push, close }) =>
        page?.id === 'projects' ? (
          <ProjectRows onOpen={close} />
        ) : (
          <CommandGroup heading="Suggestions">
            <CommandItem onSelect={() => push({ id: 'projects', label: 'Projects', placeholder: 'Search projects…' })}>
              Switch project…
            </CommandItem>
            <CommandItem onSelect={() => { deploy(); close() }}>Deploy to production</CommandItem>
          </CommandGroup>
        )
      }
    </CommandPalette>
  )
}

Render it once, near the root of the app. It's a CommandDialog with pages: the rows are yours, built from Command's parts (CommandGroup, CommandItem, CommandShortcut, CommandLoading…), and children can be a function of the palette's state. shortcut sets the key that toggles it with ⌘ or Ctrl from anywhere on the page. It's k by default, and false turns it off. The shortcut steps aside when something else on the page has already handled the keystroke.

Reference

Propschildren, open, defaultOpen… (13)

Props

CommandPalette props
PropType
openboolean–
defaultOpenboolean

default false

false
onOpenChange(open: boolean) => void–
shortcutstring | false

A key that toggles the palette with ⌘ (Ctrl elsewhere) from anywhere on the page. false for none.

default 'k'

'k'
placeholderstring

The first page's placeholder.

–
titlestring

The dialog's accessible name.

default "Command palette"

"Command palette"
descriptionstring

Describes the dialog for screen readers. Default: the built-in "Search for a command to run" (a label, so translated with the rest).

–
searchstring

Controlled search, e.g. to fetch results as the user types.

–
onSearchChange(search: string) => void–
shouldFilterboolean

Turn off cmdk's filtering when results arrive already matched.

–
autoFocusFieldboolean

Whether the search field takes focus on open. By default it does from a keyboard or a mouse and not from a touch (the list shows first). Pass true when a search button opens it to type in; on iOS also call primeKeyboard() in that button's tap handler so the keyboard comes up.

–
classNamestring–
childrenRequiredReactNode | ((state: CommandPaletteState) => ReactNode)

The rows for the current page, or a function of the palette's state.

–

useCommandPalette() returns CommandPaletteState and throws outside a palette. CommandPalettePage is { id: string; label: string; placeholder?: string }.

TouchOn a touch device the palette is a tall bottom sheet

Touch

On a touch device the palette is a tall bottom sheet. The search field is at the top, rows are 48px, and the footer of keyboard hints is gone. On a page, the breadcrumb is how you go back (its crumbs are big enough to tap). Pulling the sheet down closes the palette from any page, while Escape still steps back one page first. While the on-screen keyboard is up the sheet sits on top of it, so the search field stays visible.

The palette opens with the list in view and the keyboard down. If a search button opens it to type in, pass autoFocusField, and on iOS call primeKeyboard() from @youj/react in the button's tap handler before you open it. iOS only raises the keyboard for a focus that happens during the tap itself.

import { primeKeyboard } from '@youj/react'
import { CommandPalette } from '@youj/react/command'

<button
  onClick={() => {
    primeKeyboard()
    setOpen(true)
  }}
>
  Search
</button>
<CommandPalette open={open} onOpenChange={setOpen} autoFocusField>
  …
</CommandPalette>
MotionOpen, close, backdrop… · reduced motion

Motion

Motion tokens
MomentTokens
Openrises and scales in from the top third (yj-dialog)
duration.baseease.enter

220ms · cubic-bezier(0.16, 1, 0.3, 1)

220mscubic-bezier(0.16, 1, 0.3, 1)
Close
duration.fastease.exit

140ms · cubic-bezier(0.4, 0, 1, 1)

140mscubic-bezier(0.4, 0, 1, 1)
Backdropfades in behind it (yj-backdrop)
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)
Active rowglides as you arrow through, and across page changes
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
List heighteases between pages and result sets
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

The palette opens and closes at once, the highlight jumps and the list snaps to each new height. Pages, the breadcrumb and every key work the same.

The palette hangs from the top third of the screen, so the search field never moves while the list below it grows and shrinks between pages.

Reduced motion

The dialog appears and disappears without rising or fading, the active row's highlight jumps, and the list snaps between heights. Nothing about pages, search or the keys changes.

AccessibilityKeyboard, focus, announcements

Accessibility

  • It's a modal dialog named by title ("Command palette" by default). Focus goes to the search field when it opens and returns to where it was when it closes.
  • The search field is a combobox over a listbox, as in Command. On a page, the field is named after the page.
  • Changing page is announced ("Projects page"), and the breadcrumb is for the pointer: Backspace and Escape walk it from the keyboard.
  • The footer spells out the keys, and Escape reads Back while there's a page to go back to.
KeyAction
⌘K / Ctrl+KToggles the palette, or the key set by shortcut.
↑ / ↓Moves the active row.
EnterRuns the active row, or opens its page.
BackspaceOn an empty search, steps back one page.
EscapeSteps back one page, then closes the palette.