Skip to content

NbCommandPalette is a VS Code-style overlay for discovering and executing application commands. Any component in the tree can register commands, and users invoke them via a keyboard shortcut or programmatic API.

Live demo ​

Click the button below to open the command palette. It has been pre-loaded with sample commands. Press Escape to dismiss it, or click on any command to execute it.

TIP

This documentation site uses Ctrl+Shift+K instead of the default Cmd+K to avoid conflicting with VitePress's own search shortcut. In your application you can configure any shortcut via the openShortcut prop.

Setup ​

Install the NbCommandPalettePlugin to enable the useCommandPalette composable throughout your app.

ts
import { createApp } from 'vue'
import { NbCommandPalettePlugin } from '@nubisco/ui'
import App from '../ui/components/App.vue'

const app = createApp(App)
app.use(NbCommandPalettePlugin)
app.mount('#app')

Then place the NbCommandPalette component once at the root of your application (typically in App.vue).

vue
<template>
  <NbShell>
    <!-- your app content -->
  </NbShell>
  <NbCommandPalette />
</template>

Registering commands ​

Use the useCommandPalette composable from any component to register commands.

vue
<script setup>
import { useCommandPalette } from '@nubisco/ui'

const palette = useCommandPalette()

palette.register({
  id: 'file.save',
  label: 'Save File',
  icon: 'floppy-disk',
  namespace: 'File',
  shortcut: 'Cmd+S',
  handler: () => saveFile(),
})

palette.register({
  id: 'file.new',
  label: 'New File',
  icon: 'file-plus',
  namespace: 'File',
  shortcut: 'Cmd+N',
  handler: () => newFile(),
})

palette.register({
  id: 'edit.find',
  label: 'Find in Files',
  icon: 'magnifying-glass',
  namespace: 'Edit',
  shortcut: 'Cmd+Shift+F',
  handler: () => openSearch(),
})
</script>

Batch registration ​

Register many commands at once with registerMany.

ts
palette.registerMany([
  {
    id: 'view.sidebar',
    label: 'Toggle Sidebar',
    namespace: 'View',
    handler: toggleSidebar,
  },
  {
    id: 'view.inspector',
    label: 'Toggle Inspector',
    namespace: 'View',
    handler: toggleInspector,
  },
  {
    id: 'view.fullscreen',
    label: 'Toggle Fullscreen',
    namespace: 'View',
    handler: toggleFullscreen,
  },
])

Context-aware commands ​

Commands with a context property only appear when that context is active. Use setContext to activate a context.

ts
// Only visible when the editor context is active
palette.register({
  id: 'editor.format',
  label: 'Format Document',
  namespace: 'Editor',
  context: 'editor',
  handler: () => formatDocument(),
})

// Activate when user enters the editor
palette.setContext('editor')

// Clear context when user leaves
palette.setContext(undefined)

Opening programmatically ​

ts
// Open with empty search
palette.open()

// Open with a pre-filled filter
palette.open('save')

Configurable shortcut ​

The keyboard shortcut is fully configurable via the openShortcut prop. The value is a +-separated string of modifiers and a key.

Supported modifiers: Meta, Cmd, Ctrl, Control, Shift, Alt.

vue
<!-- Default: Meta+K (Cmd+K on Mac) -->
<NbCommandPalette />

<!-- VS Code style -->
<NbCommandPalette open-shortcut="Ctrl+Shift+p" />

<!-- Custom -->
<NbCommandPalette open-shortcut="Alt+Space" />

Keyboard navigation ​

KeyAction
Configurable shortcutOpen/close the palette
ArrowDownHighlight next result
ArrowUpHighlight previous result
EnterExecute highlighted command
EscapeAlways dismisses the palette

Searching your own data ​

Registered commands are a fixed list, which is right for actions and not enough for content: an application cannot register a command per ticket, document or customer. The optional suggest prop is asked for results on each keystroke, so one overlay answers both "what can I do" and "where is that thing".

vue
<template>
  <NbCommandPalette :suggest="findThings" />
</template>

<script setup>
import { api } from '@/api/client'

async function findThings(query) {
  const { hits } = await api.search(query)
  return hits.map((hit) => ({
    id: `hit:${hit.id}`,
    label: hit.title,
    namespace: 'Results',
    icon: 'file',
    handler: () => open(hit),
  }))
}
</script>

The palette takes care of the parts that are easy to get wrong:

  • Debounced. The suggester is asked at most once per suggestDebounce milliseconds (150 by default), not once per keystroke.
  • Race-guarded. Only the newest request may write its results. Without this a slow answer for inv can land after a fast one for invoice and replace the right results with stale ones, which reads to the user as the palette ignoring what they typed.
  • Unfiltered. Results are shown in the order the suggester returned them and are deliberately not put through the palette's own fuzzy matcher. The suggester already ranked them against the query, and a result that matched on a document's body has nothing in its label to match a second time, so re-filtering would throw it away the moment it arrived.
  • Non-fatal. If the suggester throws, the registered commands keep working. The palette is still a command palette when the network is down.

Suggested results are grouped by their namespace like any other command, and lead the list: something a person typed a name to find should not sit below an unrelated action that happens to start with an earlier letter.

TIP

Omit suggest and the palette behaves exactly as it always has. Nothing about the registered-command path changes.

Search behavior ​

The command palette uses fuzzy matching with weighted scoring:

  1. Exact prefix matches score highest
  2. Word boundary matches (start of a word) score next
  3. Substring matches score proportionally
  4. Fuzzy character-by-character matches score lowest

The search matches against the command label, namespace, and optional keywords.

NbCommandPalette ​

Props ​

PropTypeDefaultDescription
openShortcutstring'Meta+k'Keyboard shortcut string (e.g. 'Ctrl+Shift+p')
placeholderstring'Search commands...'Search input placeholder
maxResultsnumber50Maximum results to display
suggestTCommandSuggesterundefinedOptional source of query-driven results, merged in beside the registered commands
suggestDebouncenumber150Milliseconds to wait after a keystroke before asking suggest

TCommandSuggester ​

ts
type TCommandSuggester = (query: string) => ICommand[] | Promise<ICommand[]>

Called with the trimmed query whenever it is non-empty. Return commands built from whatever was found. See Searching your own data.

ICommand ​

The shape of a command object passed to register().

PropertyTypeRequiredDescription
idstringyesUnique identifier
labelstringyesDisplay label
iconstringnoPhosphor icon name
namespacestringnoGrouping label (e.g. "File", "Edit")
shortcutstringnoShortcut display text
handler() => void | Promise<void>yesFunction to execute
contextstringnoOnly show when this context is active
keywordsstring[]noAdditional search terms

useCommandPalette() ​

Returns the palette state object. Requires NbCommandPalettePlugin to be installed.

MethodSignatureDescription
register(command: ICommand) => voidRegister a single command
registerMany(commands: ICommand[]) => voidRegister multiple commands
unregister(id: string) => voidRemove a command by ID
open(filter?: string) => voidOpen the palette, optionally pre-filling the search
close() => voidClose the palette
setContext(context: string | undefined) => voidSet the active context for filtering
PropertyTypeDescription
commandsMap<string, ICommand>Reactive map of all registered commands
isOpenbooleanWhether the palette is open
activeContextstring | undefinedCurrently active context