AI Command Palette
A centralized, modal-based search bar (Cmd+K) that executes commands and features streaming AI text responses, keyboard navigation with spring animations, and a premium glass aesthetic.
Installation
Add this component to your project using the CLI:
npx vui-registry-cli-v1 add ai-command-paletteSource Code
'use client'
import React, { useState, useEffect, useRef } from 'react'
import { Command } from 'cmdk'
import { motion, AnimatePresence } from 'framer-motion'
import {
Search,
Command as CommandIcon,
Sparkles,
ArrowRight,
Zap,
Github,
Monitor,
Moon,
Sun,
Laptop,
MessageSquare,
Wand2
} from 'lucide-react'
import { cn } from '@/lib/utils'
const USER_IMAGE = "https://i.postimg.cc/3xgQH76g/Whats-App-Image-2026-02-19-at-8-23-43-PM.jpg"
// Mock data for commands
const COMMANDS = [
{
heading: "Suggestions",
items: [
{ id: 'ask-ai', icon: Sparkles, label: 'Ask AI Assistant', shortcut: 'A' },
{ id: 'search-docs', icon: Search, label: 'Search Documentation', shortcut: 'S' },
]
},
{
heading: "System",
items: [
{ id: 'toggle-theme', icon: Sun, label: 'Toggle Theme', shortcut: 'T' },
{ id: 'settings', icon: Monitor, label: 'Open Settings', shortcut: ',' },
]
},
{
heading: "Navigation",
items: [
{ id: 'home', icon: Zap, label: 'Go to Dashboard', shortcut: 'G D' },
{ id: 'projects', icon: Github, label: 'Go to Projects', shortcut: 'G P' },
]
}
]
export default function AICommandPalette() {
const [open, setOpen] = useState(false)
const [search, setSearch] = useState('')
const [selected, setSelected] = useState('')
const [mode, setMode] = useState<'command' | 'ai'>('command')
const [aiResponse, setAiResponse] = useState('')
const [isTyping, setIsTyping] = useState(false)
const [theme, setTheme] = useState('light')
const [notification, setNotification] = useState<{message: string, visible: boolean}>({ message: '', visible: false })
const showNotification = (message: string) => {
setNotification({ message, visible: true })
setTimeout(() => setNotification(prev => ({ ...prev, visible: false })), 2000)
}
const handleCommandSelect = (id: string) => {
switch (id) {
case 'ask-ai':
setMode('ai')
setSearch('')
break
case 'search-docs':
showNotification('Opening documentation...')
setTimeout(() => setOpen(false), 800)
break
case 'toggle-theme':
setTheme(prev => prev === 'light' ? 'dark' : 'light')
showNotification(`Theme switched to ${theme === 'light' ? 'Dark' : 'Light'}`)
break
case 'settings':
showNotification('Opening settings panel...')
setTimeout(() => setOpen(false), 800)
break
case 'home':
showNotification('Navigating to Dashboard...')
setTimeout(() => setOpen(false), 800)
break
case 'projects':
showNotification('Loading Projects...')
setTimeout(() => setOpen(false), 800)
break
default:
console.log('Selected:', id)
setOpen(false)
}
}
// Toggle with Cmd+K
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((open) => !open)
}
}
document.addEventListener('keydown', down)
return () => document.removeEventListener('keydown', down)
}, [])
// Simulate AI streaming response
useEffect(() => {
if (mode === 'ai' && search.length > 5 && !isTyping) {
// Debounce simulation
const timer = setTimeout(() => {
setIsTyping(true)
setAiResponse('')
const response = "Based on your request, I can help you configure the component. The 'ProScheduler' supports custom event types, drag-and-drop interactions, and premium theming options. Would you like me to generate a configuration template?"
let i = 0
const interval = setInterval(() => {
setAiResponse(prev => prev + response.charAt(i))
i++
if (i >= response.length) {
clearInterval(interval)
setIsTyping(false)
}
}, 30)
return () => clearInterval(interval)
}, 1000)
return () => clearTimeout(timer)
}
}, [mode, search])
// Reset when closing
useEffect(() => {
if (!open) {
setTimeout(() => {
setMode('command')
setSearch('')
setAiResponse('')
}, 200)
}
}, [open])
return (
<div className="h-[600px] w-full flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-950 p-4 relative overflow-hidden font-sans">
{/* Background Elements */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808008_1px,transparent_1px),linear-gradient(to_bottom,#80808008_1px,transparent_1px)] bg-[size:24px_24px]"></div>
<div className="absolute left-0 right-0 top-0 -z-10 m-auto h-[310px] w-[310px] rounded-full bg-neutral-400/20 opacity-20 blur-[100px]"></div>
{/* Trigger Button */}
<motion.button
whileHover={{ scale: 1.01, boxShadow: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)" }}
whileTap={{ scale: 0.99 }}
onClick={() => setOpen(true)}
className="group relative flex items-center gap-3 rounded-xl bg-white dark:bg-neutral-900 px-8 py-4 text-left shadow-xl shadow-neutral-200/50 dark:shadow-black/50 border border-neutral-200 dark:border-white/10 transition-all hover:border-neutral-300 dark:hover:border-white/20"
>
<Search className="w-5 h-5 text-neutral-400 group-hover:text-neutral-800 dark:group-hover:text-white transition-colors" />
<span className="text-sm font-medium text-neutral-500 group-hover:text-neutral-800 dark:text-neutral-400 dark:group-hover:text-white transition-colors">Search commands...</span>
<kbd className="ml-12 pointer-events-none inline-flex h-6 select-none items-center gap-1 rounded-md border border-neutral-200 dark:border-white/10 bg-neutral-50 dark:bg-white/5 px-2 font-mono text-[11px] font-medium text-neutral-400 dark:text-neutral-500">
<span className="text-xs">⌘</span>K
</kbd>
</motion.button>
<AnimatePresence>
{open && (
<div className="fixed inset-0 z-50 flex items-start justify-center pt-[20vh] px-4">
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setOpen(false)}
className="absolute inset-0 bg-neutral-950/20 backdrop-blur-[2px]"
/>
{/* Notification Toast */}
<AnimatePresence>
{notification.visible && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
className="absolute bottom-12 z-50 px-4 py-2 bg-neutral-900 dark:bg-white text-white dark:text-black rounded-full text-xs font-medium shadow-lg"
>
{notification.message}
</motion.div>
)}
</AnimatePresence>
{/* Command Palette */}
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
className="relative w-full max-w-2xl overflow-hidden rounded-2xl border border-neutral-200 dark:border-white/10 bg-white dark:bg-neutral-900 shadow-2xl shadow-neutral-500/10 dark:shadow-black/50"
>
<Command
filter={(value, search) => {
if (mode === 'ai') return 1
if (value.toLowerCase().includes(search.toLowerCase())) return 1
return 0
}}
className="w-full bg-transparent"
loop
>
{/* Input Area */}
<div className="flex items-center border-b border-neutral-100 dark:border-white/5 px-4 py-4">
<AnimatePresence mode="wait">
{mode === 'ai' ? (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.5 }}
className="mr-3 flex items-center justify-center w-6 h-6 rounded-lg bg-black dark:bg-white"
>
<Sparkles className="w-3.5 h-3.5 text-white dark:text-black animate-pulse" />
</motion.div>
) : (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.5 }}
>
<Search className="mr-3 h-5 w-5 text-neutral-400" />
</motion.div>
)}
</AnimatePresence>
<Command.Input
value={search}
onValueChange={setSearch}
placeholder={mode === 'ai' ? "Ask AI to generate code, explain concepts..." : "Type a command or search..."}
className="flex-1 bg-transparent text-base outline-none placeholder:text-neutral-400 dark:text-white dark:placeholder:text-neutral-600"
autoFocus
/>
{mode === 'command' && (
<div className="flex gap-2">
<button
onClick={() => setMode('ai')}
className="px-2.5 py-1.5 text-[11px] font-medium bg-black/5 dark:bg-white/10 text-neutral-600 dark:text-neutral-300 rounded-md border border-black/5 dark:border-white/5 hover:bg-black/10 dark:hover:bg-white/20 transition-colors flex items-center gap-1.5"
>
<Sparkles className="w-3 h-3" />
Ask AI
</button>
</div>
)}
{mode === 'ai' && (
<button
onClick={() => {
setMode('command')
setSearch('')
setAiResponse('')
}}
className="px-2 py-1 text-[10px] font-medium bg-neutral-100 dark:bg-white/10 text-neutral-500 dark:text-neutral-400 rounded-md hover:bg-neutral-200 dark:hover:bg-white/20 transition-colors"
>
Esc
</button>
)}
</div>
{/* Content Area */}
<div className="relative min-h-[300px] bg-neutral-50/30 dark:bg-black/20">
{mode === 'command' && (
<Command.List className="max-h-[400px] overflow-y-auto p-2 scroll-py-2 custom-scrollbar">
<Command.Empty className="py-12 text-center text-sm text-neutral-400 flex flex-col items-center gap-2">
<Search className="w-8 h-8 opacity-20" />
No results found.
</Command.Empty>
{COMMANDS.map((group) => (
<Command.Group
key={group.heading}
heading={group.heading}
className="text-[10px] font-semibold text-neutral-400 uppercase tracking-wider mb-2 px-2 mt-2 first:mt-0"
>
{group.items.map((item) => (
<Command.Item
key={item.id}
value={item.label}
onSelect={() => handleCommandSelect(item.id)}
className="group flex items-center justify-between rounded-lg px-3 py-3 text-sm text-neutral-600 dark:text-neutral-300 aria-selected:bg-white dark:aria-selected:bg-neutral-800 aria-selected:text-black dark:aria-selected:text-white aria-selected:shadow-md aria-selected:scale-[1.01] cursor-pointer transition-all duration-200 mb-1 border border-transparent aria-selected:border-neutral-200/50 dark:aria-selected:border-white/10"
>
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 rounded-md bg-neutral-100 dark:bg-white/5 border border-neutral-200 dark:border-white/5 group-aria-selected:bg-neutral-50 dark:group-aria-selected:bg-white/10 transition-colors">
<item.icon className="w-4 h-4 text-neutral-500 dark:text-neutral-400 group-aria-selected:text-neutral-900 dark:group-aria-selected:text-white" />
</div>
<span className="font-medium">{item.label}</span>
</div>
{item.shortcut && (
<span className="text-[10px] font-mono text-neutral-400 bg-neutral-100 dark:bg-white/5 px-1.5 py-0.5 rounded border border-neutral-200 dark:border-white/5 group-aria-selected:border-neutral-300 dark:group-aria-selected:border-white/10 transition-colors">
{item.shortcut}
</span>
)}
</Command.Item>
))}
</Command.Group>
))}
</Command.List>
)}
{mode === 'ai' && (
<div className="p-6 h-full flex flex-col">
{search.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center text-center text-neutral-500">
<div className="w-12 h-12 rounded-xl bg-black/5 dark:bg-white/5 flex items-center justify-center mb-4 border border-black/5 dark:border-white/5">
<Wand2 className="w-5 h-5 text-neutral-700 dark:text-neutral-300" />
</div>
<h3 className="text-sm font-semibold text-neutral-900 dark:text-white mb-1">AI Assistant Ready</h3>
<p className="text-xs max-w-[250px] text-neutral-400">Ask questions about your codebase, documentation, or generate code snippets.</p>
<div className="mt-8 flex flex-wrap justify-center gap-2">
{["How do I install this?", "Change theme colors", "Add new event type"].map((q) => (
<button
key={q}
onClick={() => setSearch(q)}
className="px-3 py-1.5 rounded-full border border-neutral-200 dark:border-white/10 text-xs text-neutral-600 dark:text-neutral-400 hover:border-neutral-400 dark:hover:border-white/30 hover:text-neutral-900 dark:hover:text-white transition-all bg-white dark:bg-white/5 shadow-sm"
>
{q}
</button>
))}
</div>
</div>
) : (
<div className="flex-1 overflow-y-auto custom-scrollbar">
<div className="flex gap-4 mb-6">
<div className="w-8 h-8 rounded-full bg-neutral-100 dark:bg-white/10 border border-neutral-200 dark:border-white/5 flex-shrink-0 flex items-center justify-center overflow-hidden">
<img src={USER_IMAGE} alt="User" className="w-full h-full object-cover" />
</div>
<div className="flex-1 pt-1.5">
<p className="text-sm text-neutral-800 dark:text-neutral-200 font-medium">{search}</p>
</div>
</div>
{(aiResponse || isTyping) && (
<div className="flex gap-4">
<div className="w-8 h-8 rounded-full bg-black dark:bg-white flex-shrink-0 flex items-center justify-center shadow-lg shadow-black/10">
<Sparkles className="w-4 h-4 text-white dark:text-black" />
</div>
<div className="flex-1 pt-1.5">
<div className="text-sm text-neutral-600 dark:text-neutral-300 leading-relaxed">
{aiResponse}
{isTyping && (
<span className="inline-block w-1.5 h-4 ml-1 bg-neutral-400 animate-pulse align-middle"></span>
)}
</div>
{!isTyping && aiResponse && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-4 flex gap-2"
>
<button
onClick={() => showNotification('Code copied to clipboard!')}
className="px-3 py-1.5 rounded-lg bg-white dark:bg-white/5 border border-neutral-200 dark:border-white/10 text-xs font-medium hover:bg-neutral-50 dark:hover:bg-white/10 transition-colors shadow-sm text-neutral-700 dark:text-neutral-300"
>
Copy Code
</button>
<button
onClick={() => showNotification('Changes applied successfully!')}
className="px-3 py-1.5 rounded-lg bg-black dark:bg-white text-white dark:text-black text-xs font-medium hover:opacity-90 transition-opacity shadow-sm"
>
Apply Changes
</button>
</motion.div>
)}
</div>
</div>
)}
</div>
)}
</div>
)}
</div>
<div className="border-t border-neutral-100 dark:border-white/5 p-2.5 flex justify-between items-center bg-white dark:bg-neutral-900 text-[10px] text-neutral-400 font-medium">
<div className="flex gap-3">
<span className="flex items-center gap-1.5">
<kbd className="bg-neutral-100 dark:bg-white/10 px-1.5 py-0.5 rounded border border-neutral-200 dark:border-white/10 font-sans">↑↓</kbd>
Navigate
</span>
<span className="flex items-center gap-1.5">
<kbd className="bg-neutral-100 dark:bg-white/10 px-1.5 py-0.5 rounded border border-neutral-200 dark:border-white/10 font-sans">↵</kbd>
Select
</span>
{mode === 'ai' && (
<span className="flex items-center gap-1.5">
<kbd className="bg-neutral-100 dark:bg-white/10 px-1.5 py-0.5 rounded border border-neutral-200 dark:border-white/10 font-sans">esc</kbd>
Back
</span>
)}
</div>
<div className="flex items-center gap-2 opacity-60">
<div className="w-1.5 h-1.5 rounded-full bg-black dark:bg-white"></div>
Velocity AI Ready
</div>
</div>
</Command>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
)
}
Dependencies
framer-motion: latestlucide-react: latestcmdk: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| open | boolean | false | Whether the palette is visible. |
| onOpenChange | (open: boolean) => void | undefined | Callback when visibility toggles. |
| mode | 'command' | 'ai' | 'command' | Initial mode of the palette. |
| commands | Array<{ heading: string; items: Array<{ id: string; icon: React.ComponentType<any>; label: string; shortcut?: string }> }> | Built-in examples | Command groups to render. |
| className | string | undefined | Additional CSS classes. |
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.

