Prompt Input

PreviousNext

A comprehensive input component for AI chat interfaces with file attachments, speech input, model selection, and more.

"use client"

import { useState } from "react"
import { GlobeIcon } from "lucide-react"

import {
  PromptInput,
  PromptInputActionAddAttachments,
  PromptInputActionMenu,
  PromptInputActionMenuContent,
  PromptInputActionMenuTrigger,
  PromptInputAttachment,
  PromptInputAttachments,
  PromptInputBody,
  PromptInputButton,
  PromptInputFooter,
  PromptInputModelSelect,
  PromptInputModelSelectItem,
  PromptInputSpeechButton,
  PromptInputSubmit,
  PromptInputTextarea,
  PromptInputTools,
} from "@/components/ai-elements/prompt-input"

const models = [
  { id: "gpt-4o", name: "GPT-4o" },
  { id: "claude-opus-4-20250514", name: "Claude 4 Opus" },
]

export function PromptInputDemo() {
  const [text, setText] = useState("")
  const [model, setModel] = useState("gpt-4o")
  const [useWebSearch, setUseWebSearch] = useState(false)
  const [status, setStatus] = useState<"ready" | "submitted" | "streaming">(
    "ready"
  )

  const handleSubmit = (message: { text?: string; files?: any[] }) => {
    if (!message.text?.trim() && !message.files?.length) {
      return
    }

    console.log("Submitted:", {
      text: message.text,
      files: message.files,
      model,
      webSearch: useWebSearch,
    })

    setStatus("submitted")
    setTimeout(() => {
      setStatus("streaming")
      setTimeout(() => {
        setStatus("ready")
        setText("")
      }, 2000)
    }, 1000)
  }

  return (
    <div className="w-full max-w-2xl">
      <PromptInput
        onSubmit={handleSubmit}
        className="relative"
        globalDrop
        multiple
      >
        <PromptInputBody>
          <PromptInputAttachments>
            {(attachment) => <PromptInputAttachment data={attachment} />}
          </PromptInputAttachments>
          <PromptInputTextarea
            onChange={(e) => setText(e.target.value)}
            value={text}
            placeholder="Ask me anything..."
          />
        </PromptInputBody>
        <PromptInputFooter>
          <PromptInputTools>
            <PromptInputActionMenu>
              <PromptInputActionMenuTrigger />
              <PromptInputActionMenuContent>
                <PromptInputActionAddAttachments />
              </PromptInputActionMenuContent>
            </PromptInputActionMenu>
            <PromptInputSpeechButton onTranscriptionChange={setText} />
            <PromptInputButton
              onClick={() => setUseWebSearch(!useWebSearch)}
              variant={useWebSearch ? "default" : "ghost"}
            >
              <GlobeIcon size={16} />
              <span>Search</span>
            </PromptInputButton>
            <PromptInputModelSelect
              selectedKey={model}
              onSelectionChange={(key) => setModel(key as string)}
              items={models}
            >
              {(item) => (
                <PromptInputModelSelectItem id={item.id}>
                  {item.name}
                </PromptInputModelSelectItem>
              )}
            </PromptInputModelSelect>
          </PromptInputTools>
          <PromptInputSubmit
            disabled={!text && status === "ready"}
            status={status}
          />
        </PromptInputFooter>
      </PromptInput>
    </div>
  )
}

Installation

pnpm dlx shadcn@latest add prompt-input

Usage

import {
  PromptInput,
  PromptInputActionAddAttachments,
  PromptInputActionMenu,
  PromptInputActionMenuContent,
  PromptInputActionMenuTrigger,
  PromptInputAttachment,
  PromptInputAttachments,
  PromptInputBody,
  PromptInputButton,
  PromptInputFooter,
  PromptInputModelSelect,
  PromptInputModelSelectItem,
  PromptInputSpeechButton,
  PromptInputSubmit,
  PromptInputTextarea,
  PromptInputTools,
} from "@/components/ai-elements/prompt-input"
const [text, setText] = useState("")
const [model, setModel] = useState("gpt-4o")
 
const models = [
  { id: "gpt-4o", name: "GPT-4o" },
  { id: "claude-opus-4-20250514", name: "Claude 4 Opus" },
]
 
<PromptInput onSubmit={(message) => console.log(message)}>
  <PromptInputBody>
    <PromptInputAttachments>
      {(attachment) => <PromptInputAttachment data={attachment} />}
    </PromptInputAttachments>
    <PromptInputTextarea
      onChange={(e) => setText(e.target.value)}
      value={text}
    />
  </PromptInputBody>
  <PromptInputFooter>
    <PromptInputTools>
      <PromptInputActionMenu>
        <PromptInputActionMenuTrigger />
        <PromptInputActionMenuContent>
          <PromptInputActionAddAttachments />
        </PromptInputActionMenuContent>
      </PromptInputActionMenu>
      <PromptInputModelSelect 
        selectedKey={model} 
        onSelectionChange={(key) => setModel(key as string)}
        items={models}
      >
        {(item) => (
          <PromptInputModelSelectItem id={item.id}>
            {item.name}
          </PromptInputModelSelectItem>
        )}
      </PromptInputModelSelect>
    </PromptInputTools>
    <PromptInputSubmit />
  </PromptInputFooter>
</PromptInput>

Examples

Default

"use client"

import { useState } from "react"
import { GlobeIcon } from "lucide-react"

import {
  PromptInput,
  PromptInputActionAddAttachments,
  PromptInputActionMenu,
  PromptInputActionMenuContent,
  PromptInputActionMenuTrigger,
  PromptInputAttachment,
  PromptInputAttachments,
  PromptInputBody,
  PromptInputButton,
  PromptInputFooter,
  PromptInputModelSelect,
  PromptInputModelSelectItem,
  PromptInputSpeechButton,
  PromptInputSubmit,
  PromptInputTextarea,
  PromptInputTools,
} from "@/components/ai-elements/prompt-input"

const models = [
  { id: "gpt-4o", name: "GPT-4o" },
  { id: "claude-opus-4-20250514", name: "Claude 4 Opus" },
]

export function PromptInputDemo() {
  const [text, setText] = useState("")
  const [model, setModel] = useState("gpt-4o")
  const [useWebSearch, setUseWebSearch] = useState(false)
  const [status, setStatus] = useState<"ready" | "submitted" | "streaming">(
    "ready"
  )

  const handleSubmit = (message: { text?: string; files?: any[] }) => {
    if (!message.text?.trim() && !message.files?.length) {
      return
    }

    console.log("Submitted:", {
      text: message.text,
      files: message.files,
      model,
      webSearch: useWebSearch,
    })

    setStatus("submitted")
    setTimeout(() => {
      setStatus("streaming")
      setTimeout(() => {
        setStatus("ready")
        setText("")
      }, 2000)
    }, 1000)
  }

  return (
    <div className="w-full max-w-2xl">
      <PromptInput
        onSubmit={handleSubmit}
        className="relative"
        globalDrop
        multiple
      >
        <PromptInputBody>
          <PromptInputAttachments>
            {(attachment) => <PromptInputAttachment data={attachment} />}
          </PromptInputAttachments>
          <PromptInputTextarea
            onChange={(e) => setText(e.target.value)}
            value={text}
            placeholder="Ask me anything..."
          />
        </PromptInputBody>
        <PromptInputFooter>
          <PromptInputTools>
            <PromptInputActionMenu>
              <PromptInputActionMenuTrigger />
              <PromptInputActionMenuContent>
                <PromptInputActionAddAttachments />
              </PromptInputActionMenuContent>
            </PromptInputActionMenu>
            <PromptInputSpeechButton onTranscriptionChange={setText} />
            <PromptInputButton
              onClick={() => setUseWebSearch(!useWebSearch)}
              variant={useWebSearch ? "default" : "ghost"}
            >
              <GlobeIcon size={16} />
              <span>Search</span>
            </PromptInputButton>
            <PromptInputModelSelect
              selectedKey={model}
              onSelectionChange={(key) => setModel(key as string)}
              items={models}
            >
              {(item) => (
                <PromptInputModelSelectItem id={item.id}>
                  {item.name}
                </PromptInputModelSelectItem>
              )}
            </PromptInputModelSelect>
          </PromptInputTools>
          <PromptInputSubmit
            disabled={!text && status === "ready"}
            status={status}
          />
        </PromptInputFooter>
      </PromptInput>
    </div>
  )
}

Features

  • File Attachments: Support for multiple file uploads with drag & drop
  • Image Preview: Preview images before sending with automatic detection
  • Auto-resize: Textarea automatically grows with content
  • Keyboard Shortcuts: Submit with Enter (Shift+Enter for new line)
  • Paste Support: Paste images directly from clipboard
  • Speech Input: Built-in voice transcription using Web Speech API
  • Model Selection: Dropdown to select AI models
  • Custom Actions: Add custom buttons and tools
  • Stop Button: Automatically shows stop button during streaming
  • Global Drop: Optional document-wide drag & drop support

Anatomy

The PromptInput component is composed of several sub-components that work together:

<PromptInput>
  <PromptInputBody>
    <PromptInputAttachments>
      {(attachment) => <PromptInputAttachment data={attachment} />}
    </PromptInputAttachments>
    <PromptInputTextarea />
  </PromptInputBody>
  <PromptInputFooter>
    <PromptInputTools>
      {/* Action menu, buttons, model select */}
    </PromptInputTools>
    <PromptInputSubmit />
  </PromptInputFooter>
</PromptInput>

API Reference

PromptInput

Main container that handles form submission and file management.

PropTypeDefaultDescription
onSubmit(message: PromptInputMessage, event: FormEvent) => void | Promise<void>-Called when form is submitted
acceptstring-File types to accept (e.g., "image/*")
multiplebooleanfalseAllow multiple file uploads
globalDropbooleanfalseEnable document-wide drag & drop
syncHiddenInputbooleanfalseKeep hidden input synced for native forms
maxFilesnumber-Maximum number of files allowed
maxFileSizenumber-Maximum file size in bytes
onError(err: { code: string, message: string }) => void-Error handler for file validation

PromptInputBody

Container for the textarea and attachments display.

PropTypeDefaultDescription
classNamestring-Custom CSS classes

PromptInputTextarea

Auto-resizing textarea that handles keyboard shortcuts.

PropTypeDefaultDescription
placeholderstring"What would you like to know?"Placeholder text
valuestring-Input value (controlled)
onChange(e: ChangeEvent<HTMLTextAreaElement>) => void-Change handler
classNamestring-Custom CSS classes

PromptInputAttachments

Displays file attachments using a render prop pattern.

PropTypeDefaultDescription
children(attachment: FileUIPart & { id: string }) => ReactNode-Render function for each attachment
classNamestring-Custom CSS classes

PromptInputAttachment

Renders a single attachment with preview and remove button.

PropTypeDefaultDescription
dataFileUIPart & { id: string }-Attachment data to display
classNamestring-Custom CSS classes

PromptInputFooter

Container for tools and submit button.

PropTypeDefaultDescription
classNamestring-Custom CSS classes

PromptInputTools

Container for action buttons and controls.

PropTypeDefaultDescription
classNamestring-Custom CSS classes

PromptInputButton

Base button component for tools.

PropTypeDefaultDescription
variantstring"ghost"Button variant
sizestringAuto-calculatedButton size
classNamestring-Custom CSS classes

PromptInputActionMenu

Dropdown menu for additional actions.

PropTypeDefaultDescription
Inherits props from DropdownMenu

PromptInputActionAddAttachments

Pre-built menu item to trigger file dialog.

PropTypeDefaultDescription
labelstring"Add photos or files"Menu item label

PromptInputSubmit

Submit button that changes based on status.

PropTypeDefaultDescription
statusChatStatus-Current chat status (shows loading/stop icon)
disabledboolean-Disable button
variantstring"default"Button variant
sizestring"icon-sm"Button size

PromptInputSpeechButton

Button with built-in speech recognition.

PropTypeDefaultDescription
textareaRefRefObject<HTMLTextAreaElement>-Ref to textarea for inserting text
onTranscriptionChange(text: string) => void-Called when transcription changes

PromptInputModelSelect

Dropdown for selecting AI models. Built on react-aria-components Select.

PropTypeDefaultDescription
selectedKeystring-Selected model key
onSelectionChange(key: Key) => void-Called when selection changes
itemsIterable<T>-List of model objects
children(item: T) => ReactNode-Render function for items
placeholderstring-Placeholder text when no selection

PromptInputModelSelectItem

Individual item in the model select dropdown.

PropTypeDefaultDescription
idstring-Unique identifier for the item
childrenReactNode-Item content

PromptInputProvider

Optional provider for global state management. When used, allows controlling input state from outside the component.

PropTypeDefaultDescription
initialInputstring""Initial text value
childrenReactNode-Child components

Hooks

usePromptInputAttachments

Access attachments context from within a PromptInput or PromptInputProvider.

const attachments = usePromptInputAttachments()
// attachments.files, attachments.add(), attachments.remove(), attachments.clear()

usePromptInputController

Access the full controller from a PromptInputProvider.

const controller = usePromptInputController()
// controller.textInput, controller.attachments