AI Input
nexvyn/ai-inputFreeComponent
AI composer textarea with auto-grow, send/stop button morph, attachment slots, and Enter-to-send.
Live preview
live · rendered by the registry
Rendered by nexvyn on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.nexvyn.dev/r/ai-input.jsonSource
1'use client'23import {4 createContext,5 forwardRef,6 useCallback,7 useContext,8 useEffect,9 useId,10 useLayoutEffect,11 useMemo,12 useRef,13 useState,14 type KeyboardEvent as ReactKeyboardEvent,15 type ReactNode,16} from 'react'17import { AnimatePresence, motion, useReducedMotion } from 'motion/react'18import { cn } from '@/lib/utils'19import { springs } from '@/lib/motion-tokens'20import {21 DropdownMenu,22 DropdownMenuContent,23 DropdownMenuGroup,24 DropdownMenuItem,25 DropdownMenuSeparator,26 DropdownMenuTrigger,27} from '@/components/ui/dropdown-menu'28import { Switch } from '@/components/ui/switch'2930const useIsoLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect3132interface AiInputMenuCoordinatorValue {33 openId: string | null34 setOpenId: (id: string | null) => void35}3637const AiInputMenuCoordinatorContext = createContext<AiInputMenuCoordinatorValue | null>(null)3839function useCoordinatedOpen(): [boolean, (open: boolean) => void] {40 const id = useId()41 const coordinator = useContext(AiInputMenuCoordinatorContext)42 const [standaloneOpen, setStandaloneOpen] = useState(false)4344 if (!coordinator) {45 return [standaloneOpen, setStandaloneOpen]46 }4748 const open = coordinator.openId === id49 const setOpen = (next: boolean) => coordinator.setOpenId(next ? id : null)50 return [open, setOpen]51}5253const ARROW_UP_PATH =54 'M222.14,103.09,131.28,20.35a12,12,0,0,0-16.56,0L23.86,103.09A12,12,0,0,0,32,124a11.86,11.86,0,0,0,8.14-3.23L116,51.44V228a12,12,0,0,0,24,0V51.44l75.86,69.33A12,12,0,1,0,232,102.77Z'5556function SendIcon({ className }: { className?: string }) {57 return (58 <svg59 viewBox="0 0 256 256"60 fill="currentColor"61 aria-hidden="true"62 className={cn('size-4', className)}63 >64 <path d={ARROW_UP_PATH} />65 </svg>66 )67}6869const STOP_PATH =70 'M180,20H76A56.06,56.06,0,0,0,20,76V180a56.06,56.06,0,0,0,56,56H180a56.06,56.06,0,0,0,56-56V76A56.06,56.06,0,0,0,180,20Z'7172function StopIcon({ className }: { className?: string }) {73 return (74 <svg75 viewBox="0 0 256 256"76 fill="currentColor"77 aria-hidden="true"78 className={cn('size-3.5', className)}79 >80 <path d={STOP_PATH} />81 </svg>82 )83}8485const MICROPHONE_PATH =86 'M128,176a48.05,48.05,0,0,0,48-48V64a48,48,0,0,0-96,0v64A48.05,48.05,0,0,0,128,176ZM96,64a32,32,0,0,1,64,0v64a32,32,0,0,1-64,0Zm40,143.6V232a8,8,0,0,1-16,0V207.6A80.11,80.11,0,0,1,48,128a8,8,0,0,1,16,0,64,64,0,0,0,128,0,8,8,0,0,1,16,0A80.11,80.11,0,0,1,136,207.6Z'8788// … truncated
What it pulls in
npm packages
Files it writes
More from nexvyn
All 40 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | nexvyn | Components | Free | 1 dep | |
| Action Buttonaction-button | nexvyn | Components | Free | 3 deps · 4 files | |
| Adaptive Actionsadaptive-actions | nexvyn | Components | Free | 1 dep | |
| Badgebadge | nexvyn | Components | Free | 2 deps · 2 files | |
| Bars Themebars-theme | nexvyn | Components | Free | 1 dep · 3 files | |
| Bounce Sidebarbounce-sidebar | nexvyn | Components | Free | 1 dep · 2 files | |
| Breadcrumbsbreadcrumbs | nexvyn | Components | Free | 1 dep · 2 files | |
| Buttonbutton | nexvyn | Components | Free | 3 deps · 3 files |
