Skip to content

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 item
  • Escape
    Close the palette, or exit sub-actions
  • Backspace
    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.

ts
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.

ts
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".

ts
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>