Skip to content
vue-cmdk
Esc
↑↓navigate↵open⌘Jpreview
On this page

Registry and shortcuts

Let components contribute their own commands, and run a command from its shortcut while the palette is closed.

A registry for screen-level commands

Some commands only make sense while a screen is showing: “Save draft” in the editor, “Export table” on the reports page. A command registry lets that screen add them while it is mounted, so there is no global list to keep in sync.

Install a registry once, next to the app:

import { createApp } from 'vue'
import { createCommandRegistry } from 'vue-cmdk-palette'

createApp(App).use(createCommandRegistry()).mount('#app')

Then register commands from any component. They are listed while the component is mounted and removed when it unmounts:

<script setup lang="ts">
import { useRegisterCommands } from 'vue-cmdk-palette'

const dirty = ref(false)

// An array, a ref or a getter. A getter is read again whenever what it uses changes.
useRegisterCommands(() => [
  { kind: 'action', id: 'save-draft', label: 'Save draft', shortcut: 'mod+s', perform: save },
  ...(dirty.value ? [{ kind: 'action' as const, id: 'discard', label: 'Discard changes', perform: discard }] : []),
])
</script>

A palette lists its own commands first, then the registered ones in registration order. They take part in search, scopes, groups and recents like any other root command, and a recent whose component is gone is skipped. With a registry in reach, the commands prop can be left out.

Scoping a registry to a subtree

provideCommandRegistry() provides a registry to the calling component’s subtree instead of the whole app, and returns it:

const registry = provideCommandRegistry()

Each registry is independent. Two apps on one page, or two server-rendered requests, never see each other’s commands.

For a headless palette, pass the registry to useCommandPalette({ registry }).

useRegisterCommands with no registry in reach does nothing. In development it logs a warning that names the missing registry.

Shortcuts that run commands

A command’s shortcut is printed on its row. Set bind-shortcuts on <CommandPaletteDialog> and a string shortcut also runs the command from anywhere on the page, while the dialog is closed:

<CommandPaletteDialog :commands="commands" bind-shortcuts />
{ kind: 'action', id: 'copy-link', label: 'Copy link', shortcut: 'mod+shift+c', perform: copyLink }
  • Only chord strings are bound. ['⌥', 'W'] and glyph strings such as '⌘K' stay display-only.
  • Root commands are bound, registered ones included. Children of a page command are not.
  • The command runs in primary mode. Actions call perform, links navigate, and a page command opens the dialog on that page. select and error are emitted as usual.
  • Shortcut runs are not added to recents.
  • While the dialog is open the shortcuts are off, and keys keep their palette meaning.
  • A chord without ⌘/Ctrl is ignored while focus is in an input, textarea, select or contenteditable element. A chord with ⌘/Ctrl still runs.
  • A disabled command doesn’t run, and its chord is left alone for the browser.

Conflicts

The dialog’s own hotkey always wins over a command chord. When two commands share a chord, the first in list order runs. In development both cases log a warning that names the chord and the commands.

Headless and inline palettes

<CommandPalette> doesn’t bind shortcuts, since an inline palette is usually part of a page with its own key handling. useCommandShortcuts does the same binding for your own markup:

import { useCommandShortcuts } from 'vue-cmdk-palette'

useCommandShortcuts(commands, {
  run: (command) => palette.activate(command),
  enabled: () => !isOpen.value,
})

It follows the same matching, editable-field and disabled rules as the dialog.

Was this page helpful?