InputsNEW
AI Input
#input#ai#textarea
Press Enter to send · Shift+Enter for new line
implementedInfinityUI component
This component is fully implemented in InfinityUI and wired into the docs and registry flow.
Installation
Use the registry command to add the component source, and install any package dependencies if needed.
Infinity Registry
Install the component source into your project with the shadcn CLI.
npx shadcn@latest add https://infinityui-pearl.vercel.app/r/ai-inputPackage Dependencies
Install the npm packages used by this component source.
npm install @iconify/react framer-motionComponent Code
Copy and paste this code into your component file.
tsx
"use client";
import { useState, useRef, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Icon } from "@iconify/react";
interface AIInputProps {
onSubmit?: (value: string) => void;
placeholder?: string;
}
const SUGGESTIONS = [
"Explain quantum computing in simple terms",
"Write a poem about the ocean",
"How do I center a div in CSS?",
"Create a marketing plan for a startup",
];
export function AIInput({ onSubmit, placeholder = "Ask anything..." }: AIInputProps) {
const [value, setValue] = useState("");
const [focused, setFocused] = useState(false);
const [suggIndex, setSuggIndex] = useState(0);
const [typing, setTyping] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Cycle suggestions in placeholder
useEffect(() => {
if (focused || value) return;
const t = setInterval(() => setSuggIndex((i) => (i + 1) % SUGGESTIONS.length), 3000);
return () => clearInterval(t);
}, [focused, value]);
// Auto-resize
useEffect(() => {
if (!textareaRef.current) return;
textareaRef.current.style.height = "auto";
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
}, [value]);
const handleSubmit = () => {
if (!value.trim()) return;
onSubmit?.(value);
setValue("");
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
};
return (
<div className="w-full max-w-xl">
<motion.div
animate={{ boxShadow: focused ? "0 0 0 1px rgba(99,102,241,0.5), 0 8px 40px rgba(99,102,241,0.12)" : "0 0 0 1px rgba(255,255,255,0.08), 0 4px 20px rgba(0,0,0,0.3)" }}
className="relative rounded-2xl bg-zinc-900 border border-white/10 overflow-hidden"
>
{/* Animated top gradient line */}
{focused && (
<motion.div
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
className="absolute top-0 inset-x-0 h-px bg-gradient-to-r from-transparent via-indigo-500 to-transparent origin-left"
/>
)}
<textarea
ref={textareaRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
rows={1}
className="w-full bg-transparent text-white text-sm resize-none outline-none px-4 pt-4 pb-3 leading-relaxed placeholder:text-zinc-600 min-h-[56px]"
placeholder={focused ? placeholder : SUGGESTIONS[suggIndex]}
/>
<div className="flex items-center justify-between px-3 pb-3">
<div className="flex items-center gap-2 text-zinc-600">
<button className="hover:text-zinc-400 transition-colors p-1.5 rounded-lg hover:bg-white/5" aria-label="Attach file">
<Icon icon="solar:paperclip-linear" fontSize={16} />
</button>
<button className="hover:text-zinc-400 transition-colors p-1.5 rounded-lg hover:bg-white/5" aria-label="Voice input">
<Icon icon="solar:microphone-2-linear" fontSize={16} />
</button>
</div>
<div className="flex items-center gap-2">
{value && (
<span className="text-[11px] text-zinc-600">↵ Enter</span>
)}
<motion.button
onClick={handleSubmit}
whileTap={{ scale: 0.9 }}
disabled={!value.trim()}
className={`w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-200 ${value.trim()
? "bg-indigo-500 text-white hover:bg-indigo-400 shadow-[0_0_15px_rgba(99,102,241,0.4)]"
: "bg-zinc-800 text-zinc-600 cursor-not-allowed"
}`}
aria-label="Send message"
>
<Icon icon="solar:arrow-up-linear" fontSize={16} />
</motion.button>
</div>
</div>
</motion.div>
<p className="text-center text-xs text-zinc-700 mt-2">Press Enter to send · Shift+Enter for new line</p>
</div>
);
}