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.
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).
<template>
<NbShell>
<!-- your app content -->
</NbShell>
<NbCommandPalette />
</template>Registering commands
Use the useCommandPalette composable from any component to register commands.
<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.
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.
// 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
// 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.
<!-- 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
| Key | Action |
|---|---|
| Configurable shortcut | Open/close the palette |
ArrowDown | Highlight next result |
ArrowUp | Highlight previous result |
Enter | Execute highlighted command |
Escape | Always 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".
<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
suggestDebouncemilliseconds (150 by default), not once per keystroke. - Race-guarded. Only the newest request may write its results. Without this a slow answer for
invcan land after a fast one forinvoiceand 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:
- Exact prefix matches score highest
- Word boundary matches (start of a word) score next
- Substring matches score proportionally
- Fuzzy character-by-character matches score lowest
The search matches against the command label, namespace, and optional keywords.