Command Palette
A searchable command palette that provides quick access to navigation, actions, and entity search through a unified interface. It can optionally be opened with a global ⌘K (Mac) or Ctrl+K (Windows/Linux) keyboard shortcut by setting the has-keyboard-shortcut prop.
Items are organized through sources, where each source represents a group of related items. Sources can optionally appear as horizontal tabs for scoped filtering. Items within a source can have sub-actions, which are shown after selecting the item.
Props
has-keyboard-shortcut?: boolean
Enables the global keyboard shortcut (⌘K / Ctrl+K) to open and close the command palette.
placeholder?: string
Custom placeholder text for the search input.
sources: FluxCommandSource[]
The data sources that provide items to the command palette.
Emits
select: []
Emitted when an item is activated.
Keyboard shortcuts
⌘K / Ctrl+K
Open / close the command palette (requires has-keyboard-shortcut)↑ / ↓
Navigate through items← / →
Navigate through tabs (when search is empty)Enter
Activate the highlighted itemEscape
Close the palette, or exit sub-actionsBackspace
Go back one breadcrumb level (when search is empty)
Sources
Sources are the core building blocks of the command palette. Each source has a key, label, and a list of items. Set tab: true to show the source as a tab in the tab bar. Use an empty label to hide the group header for a source.
type FluxCommandSource = {
readonly key: string;
readonly label: string;
readonly icon?: FluxIconName;
readonly tab?: boolean;
readonly items: FluxCommandSourceItem[];
readonly fetchSearch?: (query: string) => Promise<FluxCommandSourceItem[]>;
};Async sources
A source can provide a fetchSearch function to load items asynchronously when the user types a search query. Static items are shown when the search is empty, and fetchSearch results replace them when a query is entered. A centered spinner is displayed while the fetch is in progress.
Items
Each item has a label, optional icon, and an onActivate callback. Items can also have a command string (e.g. ⌘D) displayed as a keyboard shortcut badge, and subActions for secondary actions.
type FluxCommandSourceItem = {
readonly id: string | number;
readonly label: string;
readonly subLabel?: string;
readonly icon?: FluxIconName;
readonly command?: string;
readonly subActions?: FluxCommandSubAction[];
readonly onActivate: () => void;
};Sub-actions
When an item has subActions, selecting it will show a sub-action menu instead of immediately activating the item. This is useful for entities that have multiple possible actions, such as "View", "Edit", or "Delete".
type FluxCommandSubAction = {
readonly label: string;
readonly icon?: FluxIconName;
readonly onActivate: () => void;
};Examples
Basic
A basic command palette with navigation and actions. Press `⌘K` or click the button to open.
<template>
<FluxSecondaryButton
label="Open command palette"
@click="commandPalette?.open()"/>
<FluxCommandPalette
ref="commandPalette"
:sources="sources"/>
</template>
<script
lang="ts"
setup>
import type { FluxCommandSource } from '@flux-ui/types';
import { FluxCommandPalette, FluxSecondaryButton, showSnackbar } from '@flux-ui/components';
import { ref } from 'vue';
const commandPalette = ref<InstanceType<typeof FluxCommandPalette>>();
const activate = (label: string) => showSnackbar({
icon: 'circle-check',
message: `Activated: ${label}`
});
const sources: FluxCommandSource[] = [
{
key: 'navigation',
label: '',
items: [
{
id: 'dashboard',
label: 'Dashboard',
icon: 'grid-2',
onActivate: () => activate('Dashboard')
},
{
id: 'settings',
label: 'Settings',
icon: 'gear',
onActivate: () => activate('Settings')
},
{
id: 'users',
label: 'Users',
icon: 'users',
onActivate: () => activate('Users')
}
]
},
{
key: 'actions',
label: 'Actions',
tab: true,
items: [
{
id: 'dark-mode',
label: 'Toggle dark mode',
icon: 'moon',
command: '\u2318D',
onActivate: () => activate('Toggle dark mode')
},
{
id: 'logout',
label: 'Log out',
icon: 'arrow-right-from-bracket',
onActivate: () => activate('Log out')
}
]
}
];
</script>Multiple sources
A command palette with multiple sources, tabs, and sub-actions.
<template>
<FluxSecondaryButton
label="Open command palette"
@click="commandPalette?.open()"/>
<FluxCommandPalette
ref="commandPalette"
placeholder="Search customers, orders..."
:sources="sources"/>
</template>
<script
lang="ts"
setup>
import type { FluxCommandSource } from '@flux-ui/types';
import { FluxCommandPalette, FluxSecondaryButton, showSnackbar } from '@flux-ui/components';
import { ref } from 'vue';
const commandPalette = ref<InstanceType<typeof FluxCommandPalette>>();
const activate = (label: string) => showSnackbar({
icon: 'circle-check',
message: `Activated: ${label}`
});
const sources: FluxCommandSource[] = [
{
key: 'navigation',
label: '',
items: [
{
id: 'dashboard',
label: 'Dashboard',
icon: 'grid-2',
onActivate: () => activate('Dashboard')
},
{
id: 'settings',
label: 'Settings',
icon: 'gear',
onActivate: () => activate('Settings')
}
]
},
{
key: 'customers',
label: 'Customers',
icon: 'users',
tab: true,
items: [
{
id: 'c1',
label: 'Acme Corp',
subLabel: 'Enterprise customer',
icon: 'building',
subActions: [
{
label: 'View details',
icon: 'eye',
onActivate: () => activate('View Acme Corp')
},
{
label: 'Edit customer',
icon: 'pen',
onActivate: () => activate('Edit Acme Corp')
},
{
label: 'View orders',
icon: 'box',
onActivate: () => activate('View orders for Acme Corp')
}
],
onActivate: () => activate('Acme Corp')
},
{
id: 'c2',
label: 'Globex Inc',
subLabel: 'SMB customer',
icon: 'building',
onActivate: () => activate('Globex Inc')
}
]
},
{
key: 'orders',
label: 'Orders',
icon: 'box',
tab: true,
items: [
{
id: 'o1',
label: 'Order #1234',
subLabel: 'Acme Corp',
icon: 'box',
onActivate: () => activate('Order #1234')
},
{
id: 'o2',
label: 'Order #1235',
subLabel: 'Globex Inc',
icon: 'box',
onActivate: () => activate('Order #1235')
}
]
},
{
key: 'actions',
label: 'Actions',
tab: true,
items: [
{
id: 'dark-mode',
label: 'Toggle dark mode',
icon: 'moon',
command: '\u2318D',
onActivate: () => activate('Toggle dark mode')
}
]
}
];
</script>Async
A command palette with an async source that fetches customers from a simulated API. Static items are shown initially, and search results are loaded dynamically.
<template>
<FluxSecondaryButton
label="Open command palette"
@click="commandPalette?.open()"/>
<FluxCommandPalette
ref="commandPalette"
placeholder="Search customers, pages..."
:sources="sources"/>
</template>
<script
lang="ts"
setup>
import type { FluxCommandSource, FluxCommandSourceItem } from '@flux-ui/types';
import { FluxCommandPalette, FluxSecondaryButton, showSnackbar } from '@flux-ui/components';
import { ref } from 'vue';
const commandPalette = ref<InstanceType<typeof FluxCommandPalette>>();
const activate = (label: string) => showSnackbar({
icon: 'circle-check',
message: `Activated: ${label}`
});
const customers = [
{id: 1, name: 'Acme Corp', segment: 'Enterprise · New York'},
{id: 2, name: 'Globex Inc', segment: 'SMB · London'},
{id: 3, name: 'Stark Industries', segment: 'Enterprise · Los Angeles'},
{id: 4, name: 'Wayne Enterprises', segment: 'Enterprise · Gotham'},
{id: 5, name: 'Umbrella Corp', segment: 'SMB · Raccoon City'},
{id: 6, name: 'Initech', segment: 'SMB · Austin'},
{id: 7, name: 'Hooli', segment: 'Enterprise · Palo Alto'},
{id: 8, name: 'Pied Piper', segment: 'Startup · Palo Alto'},
{id: 9, name: 'Dunder Mifflin', segment: 'SMB · Scranton'},
{id: 10, name: 'Sterling Cooper', segment: 'SMB · New York'}
];
const sources: FluxCommandSource[] = [
{
key: 'navigation',
label: '',
items: [
{id: 'dashboard', label: 'Dashboard', icon: 'grid-2', onActivate: () => activate('Dashboard')},
{id: 'customers', label: 'Customers', icon: 'users', onActivate: () => activate('Customers')},
{id: 'settings', label: 'Settings', icon: 'gear', onActivate: () => activate('Settings')}
]
},
{
key: 'customers',
label: 'Customers',
icon: 'users',
tab: true,
items: [
{id: 'c1', label: 'Acme Corp', subLabel: 'Enterprise · New York', icon: 'building', onActivate: () => activate('Acme Corp')},
{id: 'c2', label: 'Globex Inc', subLabel: 'SMB · London', icon: 'building', onActivate: () => activate('Globex Inc')},
{id: 'c3', label: 'Stark Industries', subLabel: 'Enterprise · Los Angeles', icon: 'building', onActivate: () => activate('Stark Industries')}
],
fetchSearch: async (query: string): Promise<FluxCommandSourceItem[]> => {
await new Promise(resolve => setTimeout(resolve, 1000));
return customers
.filter(c => c.name.toLowerCase().includes(query.toLowerCase()) || c.segment.toLowerCase().includes(query.toLowerCase()))
.map(c => ({
id: `c${c.id}`,
label: c.name,
subLabel: c.segment,
icon: 'building',
onActivate: () => activate(c.name)
}));
}
}
];
</script>Complex
A full-featured command palette with navigation, entity search, standalone actions with sub-menus (Theme, Language, Export), recent items, and keyboard shortcut badges.
<template>
<FluxSecondaryButton
label="Open command palette"
@click="commandPalette?.open()"/>
<FluxCommandPalette
ref="commandPalette"
placeholder="What are you looking for?"
:sources="sources"/>
</template>
<script
lang="ts"
setup>
import type { FluxCommandSource } from '@flux-ui/types';
import { FluxCommandPalette, FluxSecondaryButton, showSnackbar } from '@flux-ui/components';
import { ref } from 'vue';
const commandPalette = ref<InstanceType<typeof FluxCommandPalette>>();
const activate = (label: string) => showSnackbar({
icon: 'circle-check',
message: `Activated: ${label}`
});
const sources: FluxCommandSource[] = [
{
key: 'navigation',
label: '',
items: [
{
id: 'nav-dashboard',
label: 'Dashboard',
icon: 'grid-2',
onActivate: () => activate('Dashboard')
},
{
id: 'nav-customers',
label: 'Customers',
icon: 'users',
onActivate: () => activate('Customers')
},
{
id: 'nav-orders',
label: 'Orders',
icon: 'box',
onActivate: () => activate('Orders')
},
{
id: 'nav-analytics',
label: 'Analytics',
icon: 'chart-line',
onActivate: () => activate('Analytics')
},
{
id: 'nav-settings',
label: 'Settings',
icon: 'gear',
onActivate: () => activate('Settings')
}
]
},
{
key: 'customers',
label: 'Customers',
icon: 'users',
tab: true,
items: [
{
id: 'c1',
label: 'Acme Corp',
subLabel: 'Enterprise \u00b7 New York',
icon: 'building',
subActions: [
{label: 'View details', icon: 'eye', onActivate: () => activate('View Acme Corp')},
{label: 'Edit customer', icon: 'pen', onActivate: () => activate('Edit Acme Corp')},
{label: 'View orders', icon: 'box', onActivate: () => activate('Orders for Acme Corp')},
{label: 'Send message', icon: 'message', onActivate: () => activate('Message Acme Corp')}
],
onActivate: () => activate('Acme Corp')
},
{
id: 'c2',
label: 'Globex Inc',
subLabel: 'SMB \u00b7 London',
icon: 'building',
subActions: [
{label: 'View details', icon: 'eye', onActivate: () => activate('View Globex Inc')},
{label: 'Edit customer', icon: 'pen', onActivate: () => activate('Edit Globex Inc')}
],
onActivate: () => activate('Globex Inc')
},
{
id: 'c3',
label: 'Stark Industries',
subLabel: 'Enterprise \u00b7 Los Angeles',
icon: 'building',
onActivate: () => activate('Stark Industries')
},
{
id: 'c4',
label: 'Wayne Enterprises',
subLabel: 'Enterprise \u00b7 Gotham',
icon: 'building',
onActivate: () => activate('Wayne Enterprises')
},
{
id: 'c5',
label: 'Umbrella Corp',
subLabel: 'SMB \u00b7 Raccoon City',
icon: 'building',
onActivate: () => activate('Umbrella Corp')
}
]
},
{
key: 'orders',
label: 'Orders',
icon: 'box',
tab: true,
items: [
{
id: 'o1',
label: 'Order #4201',
subLabel: 'Acme Corp \u00b7 Processing',
icon: 'box',
onActivate: () => activate('Order #4201')
},
{
id: 'o2',
label: 'Order #4202',
subLabel: 'Globex Inc \u00b7 Shipped',
icon: 'truck',
onActivate: () => activate('Order #4202')
},
{
id: 'o3',
label: 'Order #4203',
subLabel: 'Stark Industries \u00b7 Delivered',
icon: 'circle-check',
onActivate: () => activate('Order #4203')
},
{
id: 'o4',
label: 'Order #4204',
subLabel: 'Wayne Enterprises \u00b7 Processing',
icon: 'box',
onActivate: () => activate('Order #4204')
}
]
},
{
key: 'actions',
label: 'Actions',
icon: 'bolt',
tab: true,
items: [
{
id: 'a-theme',
label: 'Theme',
icon: 'sun',
subActions: [
{label: 'Light', icon: 'sun', onActivate: () => activate('Theme: Light')},
{label: 'Dark', icon: 'moon', onActivate: () => activate('Theme: Dark')},
{label: 'System', icon: 'desktop', onActivate: () => activate('Theme: System')}
],
onActivate: () => activate('Theme')
},
{
id: 'a-language',
label: 'Language',
icon: 'globe',
subActions: [
{label: 'English', onActivate: () => activate('Language: English')},
{label: 'Dutch', onActivate: () => activate('Language: Dutch')},
{label: 'German', onActivate: () => activate('Language: German')},
{label: 'French', onActivate: () => activate('Language: French')}
],
onActivate: () => activate('Language')
},
{
id: 'a-new-customer',
label: 'Create customer',
icon: 'user-plus',
command: '\u2318N',
onActivate: () => activate('Create customer')
},
{
id: 'a-export',
label: 'Export data',
icon: 'arrow-up-from-square',
subActions: [
{label: 'Export as CSV', onActivate: () => activate('Export CSV')},
{label: 'Export as Excel', onActivate: () => activate('Export Excel')},
{label: 'Export as PDF', onActivate: () => activate('Export PDF')}
],
onActivate: () => activate('Export data')
},
{
id: 'a-copy-url',
label: 'Copy current URL',
icon: 'copy',
command: '\u2318\u21e7C',
onActivate: () => activate('Copy current URL')
},
{
id: 'a-logout',
label: 'Log out',
icon: 'arrow-right-from-bracket',
onActivate: () => activate('Log out')
}
]
},
{
key: 'recent',
label: 'Recent',
icon: 'rectangle-history',
tab: true,
items: [
{
id: 'r1',
label: 'Order #4201',
subLabel: 'Viewed 5 minutes ago',
icon: 'box',
onActivate: () => activate('Order #4201')
},
{
id: 'r2',
label: 'Acme Corp',
subLabel: 'Edited 15 minutes ago',
icon: 'building',
onActivate: () => activate('Acme Corp')
},
{
id: 'r3',
label: 'Analytics',
subLabel: 'Viewed 1 hour ago',
icon: 'chart-line',
onActivate: () => activate('Analytics')
}
]
}
];
</script>