UIPackage
Menu

Command Palette

block dashboard
Edit on GitHub

Header-grade search/command palette. Bundles the slim trigger button (with platform-aware ⌘K/Ctrl-K kbd hint) AND the modal CommandDialog into one block; consumers drop it once and the global keyboard shortcut wires itself. Takes a `groups` array of `{ heading, items: [{ label, hint, icon, onSelect }] }` and emits `select`. `show-trigger=false` hides the inline button so the consumer can fire it from elsewhere via the exposed `show()` / `toggle()` methods.

Also available for React ->

Installation

$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/command-palette.json
Named registry: npx shadcn-vue@latest add @uipkge/command-palette Installs to: app/components/blocks/

Examples

Props

Name Type / Values Default Required
groups CommandPaletteGroup[] () => [ { heading: 'Navigate', items: [ { label: 'Dashboa… optional
placeholder string 'Search pages optional
triggerLabel string 'Search pages optional
showTrigger boolean true optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

CommandPaletteItem
interface CommandPaletteItem {
  label: string
  value?: string
  hint?: string
  icon?: Component
  onSelect?: () => void
}
CommandPaletteGroup
interface CommandPaletteGroup {
  heading: string
  items: CommandPaletteItem[]
}

npm dependencies

Includes

Used by

Files installed (1)

  • app/components/blocks/CommandPalette.vue 4 kB
    <script setup lang="ts">
    import { computed, onBeforeUnmount, onMounted, ref, type Component } from 'vue'
    import { LayoutDashboard, FileText, Inbox, Settings, Users, KanbanSquare, Search } from 'lucide-vue-next'
    import {
      CommandDialog,
      CommandEmpty,
      CommandGroup,
      CommandInput,
      CommandItem,
      CommandList,
      CommandSeparator,
      CommandShortcut,
    } from '@/components/ui/command'
    
    export interface CommandPaletteItem {
      label: string
      value?: string
      hint?: string
      icon?: Component
      onSelect?: () => void
    }
    
    export interface CommandPaletteGroup {
      heading: string
      items: CommandPaletteItem[]
    }
    
    const props = withDefaults(
      defineProps<{
        groups?: CommandPaletteGroup[]
        placeholder?: string
        triggerLabel?: string
        showTrigger?: boolean
      }>(),
      {
        placeholder: 'Search pages, commands…',
        triggerLabel: 'Search pages, commands…',
        showTrigger: true,
        groups: () => [
          {
            heading: 'Navigate',
            items: [
              { label: 'Dashboard', hint: '/dashboard', icon: LayoutDashboard },
              { label: 'Inbox', hint: '/inbox', icon: Inbox },
              { label: 'Kanban', hint: '/kanban', icon: KanbanSquare },
              { label: 'Team', hint: '/team', icon: Users },
            ],
          },
          {
            heading: 'Settings',
            items: [
              { label: 'Profile', hint: '/profile', icon: FileText },
              { label: 'Settings', hint: '/settings', icon: Settings },
            ],
          },
        ],
      },
    )
    
    const emit = defineEmits<{
      (e: 'select', item: CommandPaletteItem): void
    }>()
    
    const open = ref(false)
    
    function show() {
      open.value = true
    }
    
    function hide() {
      open.value = false
    }
    
    function toggle() {
      open.value = !open.value
    }
    
    function pick(item: CommandPaletteItem) {
      hide()
      item.onSelect?.()
      emit('select', item)
    }
    
    function onKeydown(e: KeyboardEvent) {
      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
        e.preventDefault()
        toggle()
      }
    }
    
    const triggerShortcut = computed(() => {
      if (typeof navigator === 'undefined') return '⌘K'
      return /Mac|iPhone|iPad/i.test(navigator.platform) ? '⌘K' : 'Ctrl K'
    })
    
    onMounted(() => window.addEventListener('keydown', onKeydown))
    onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
    
    defineExpose({ show, hide, toggle })
    </script>
    
    <template>
      <button
        v-if="showTrigger"
        type="button"
        class="bg-secondary/50 hover:bg-secondary text-muted-foreground focus-visible:ring-ring relative hidden h-8 w-full items-center gap-2 rounded-lg border border-transparent px-2.5 text-sm shadow-none transition-colors focus-visible:ring-1 focus-visible:outline-none sm:flex md:w-[220px] lg:w-[300px]"
        aria-label="Open command palette"
        @click="show"
      >
        <Search class="size-3.5 shrink-0" />
        <span class="flex-1 truncate text-left">{{ triggerLabel }}</span>
        <kbd
          class="bg-muted/80 text-muted-foreground pointer-events-none flex h-5 items-center justify-center rounded-md border px-1.5 font-mono text-[10px] font-medium"
        >
          <span>{{ triggerShortcut }}</span>
        </kbd>
      </button>
    
      <CommandDialog v-model:open="open" title="Command palette" description="Search pages and run commands">
        <CommandInput :placeholder="placeholder" />
        <CommandList class="max-h-[480px]">
          <CommandEmpty>No matches.</CommandEmpty>
          <template v-for="(group, gi) in groups" :key="group.heading">
            <CommandGroup :heading="group.heading">
              <CommandItem
                v-for="item in group.items"
                :key="`${group.heading}-${item.label}`"
                :value="`${group.heading} ${item.label} ${item.hint ?? ''}`"
                @select="pick(item)"
              >
                <component :is="item.icon" v-if="item.icon" class="size-4" />
                <span>{{ item.label }}</span>
                <CommandShortcut v-if="item.hint" class="text-muted-foreground/70">{{ item.hint }}</CommandShortcut>
              </CommandItem>
            </CommandGroup>
            <CommandSeparator v-if="gi < groups.length - 1" />
          </template>
        </CommandList>
      </CommandDialog>
    </template>

Raw manifest: https://uipkge.dev/r/vue/command-palette.json