UIPackage

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 calls `onSelect`. `showTrigger={false}` hides the inline button so the consumer can control open state from elsewhere.

Also available for Vue ->

Installation

$ npx shadcn@latest add https://react.uipkge.dev/r/react/command-palette.json

Or with the named registry: npx shadcn@latest add @uipkge-react/command-palette

Examples

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?: LucideIcon
  onSelect?: () => void
}
CommandPaletteGroup
interface CommandPaletteGroup {
  heading: string
  items: CommandPaletteItem[]
}

npm dependencies

Includes

Files (1)

  • components/blocks/CommandPalette.tsx 4.4 kB
    'use client'
    
    import * as React from 'react'
    import {
      LayoutDashboard,
      FileText,
      Inbox,
      Settings,
      Users,
      KanbanSquare,
      Search,
      type LucideIcon,
    } from 'lucide-react'
    import {
      CommandDialog,
      CommandEmpty,
      CommandGroup,
      CommandInput,
      CommandItem,
      CommandList,
      CommandSeparator,
      CommandShortcut,
    } from '@/components/ui/command'
    
    export interface CommandPaletteItem {
      label: string
      value?: string
      hint?: string
      icon?: LucideIcon
      onSelect?: () => void
    }
    
    export interface CommandPaletteGroup {
      heading: string
      items: CommandPaletteItem[]
    }
    
    export interface CommandPaletteProps {
      groups?: CommandPaletteGroup[]
      placeholder?: string
      triggerLabel?: string
      showTrigger?: boolean
      onSelect?: (item: CommandPaletteItem) => void
    }
    
    const defaultGroups: CommandPaletteGroup[] = [
      {
        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 },
        ],
      },
    ]
    
    export function CommandPalette({
      groups = defaultGroups,
      placeholder = 'Search pages, commands…',
      triggerLabel = 'Search pages, commands…',
      showTrigger = true,
      onSelect,
    }: CommandPaletteProps) {
      const [open, setOpen] = React.useState(false)
      const [triggerShortcut, setTriggerShortcut] = React.useState('⌘K')
    
      function pick(item: CommandPaletteItem) {
        setOpen(false)
        item.onSelect?.()
        onSelect?.(item)
      }
    
      React.useEffect(() => {
        function onKeydown(e: KeyboardEvent) {
          if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
            e.preventDefault()
            setOpen((prev) => !prev)
          }
        }
        window.addEventListener('keydown', onKeydown)
        return () => window.removeEventListener('keydown', onKeydown)
      }, [])
    
      React.useEffect(() => {
        if (typeof navigator === 'undefined') return
        setTriggerShortcut(/Mac|iPhone|iPad/i.test(navigator.platform) ? '⌘K' : 'Ctrl K')
      }, [])
    
      return (
        <>
          {showTrigger && (
            <button
              type="button"
              className="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"
              onClick={() => setOpen(true)}
            >
              <Search className="size-3.5 shrink-0" />
              <span className="flex-1 truncate text-left">{triggerLabel}</span>
              <kbd className="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
            open={open}
            onOpenChange={setOpen}
            title="Command palette"
            description="Search pages and run commands"
          >
            <CommandInput placeholder={placeholder} />
            <CommandList className="max-h-[480px]">
              <CommandEmpty>No matches.</CommandEmpty>
              {groups.map((group, gi) => (
                <React.Fragment key={group.heading}>
                  <CommandGroup heading={group.heading}>
                    {group.items.map((item) => {
                      const Icon = item.icon
                      return (
                        <CommandItem
                          key={`${group.heading}-${item.label}`}
                          value={`${group.heading} ${item.label} ${item.hint ?? ''}`}
                          onSelect={() => pick(item)}
                        >
                          {Icon && <Icon className="size-4" />}
                          <span>{item.label}</span>
                          {item.hint && (
                            <CommandShortcut className="text-muted-foreground/70">{item.hint}</CommandShortcut>
                          )}
                        </CommandItem>
                      )
                    })}
                  </CommandGroup>
                  {gi < groups.length - 1 && <CommandSeparator />}
                </React.Fragment>
              ))}
            </CommandList>
          </CommandDialog>
        </>
      )
    }

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