INFINITYUI
HomeComponentsDocsTemplates
Star

Getting Started

  • Introduction
  • Installation
  • CLI
  • Audit

Navigation

  • Spotlight Navbar
  • Glass DockNEW
  • Animated Tab Bar
  • Circle Menu
  • Magnet Tabs
  • Animated Sidebar
  • Apple Spotlight
  • Page TOC RailNEW

Text

  • Flip Text
  • Glitch Text
  • Liquid Text
  • Flip Fade Text
  • Mask Cursor Effect

Cards

  • Glow Border Card
  • Testimonials Card
  • Interactive Book
  • Trading CardsNEW
  • Hover Image
  • Chain of ThoughtNEW
  • Masonry Grid
  • Image Pile
  • Staggered Grid

Inputs

  • AI InputNEW
  • OTP Input
  • Leave Rating

Buttons

  • Social Flip Button
  • Creepy Button

Loaders

  • Jelly Loader
  • Rolling Ball Scroll
  • Glowing Scroll

Backgrounds

  • Light Lines
  • Perspective Grid
  • Liquid Ocean
  • Eagle Vision
  • Flow Scroll
  • Horizontal Scroll

Overlays

  • PersonaNEW
  • Infinite Moving Cards
  • Masked Avatars
  • Stacked Logos
  • Icon Wheel
  • Pixelated CarouselNEW
  • Pixelated Image Trail
  • Flip Scroll
  • Interactive Folder
  • Animated Folder IconNEW
  • Stack Scroll
  • Rubik Cube
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-input
Package Dependencies

Install the npm packages used by this component source.

npm install @iconify/react framer-motion

Component 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>
    );
}