Prompt Input
A chat composer that grows as you type. Handles file uploads, @ and / menus at the caret and messages typed while a reply streams.
The composer is the part of a chat people touch most, so every keystroke has to feel right. Prompt Input starts as one calm line with the + on the left and send on the right, grows into a card once the text wraps or files are added, and settles back when cleared.
Enter sends and Shift+Enter adds a line, and input methods for Japanese, Chinese and Korean are never interrupted. On touch screens Enter adds a line, since the keyboard has no Shift. Inserts go through the browser’s own undo stack, so ⌘Z always works, and long pastes become an attachment you can preview or put back inline.
Everything is composed from parts that share one context. Use just the field and send button, or add file uploads with progress and retry, @ and / menus at the caret, a model picker, an effort slider, a context ring with a token and cost breakdown, dictation, tool chips, a queue for messages sent while a reply streams, and drafts that survive a reload.
Prompt Input is free. Install it without an account or a token, and use it under the Pro License in as many projects as you like.
pnpm dlx shadcn@latest add https://hextaui.com/r/pro/prompt-input.jsonBuild the rest of the chat
Prompt Input is free. These blocks build the rest of the chat around it, and come with HextaUI Pro.
- Chat ThreadThe conversation view of an AI chat. Handles replies that stream, edit and retry, and versions of a reply you can switch between.
- StreamingText that streams from a model at a steady pace. Bursty chunks are smoothed out, with a fade on new words and a caret while it writes.
- ThinkingThe thinking view of an AI chat. A live label follows each step, searches and sources fold into a trail, and citations sit beside the answer.
- Tool CallsThe list of what an agent does, one line per step. Reads fold into a summary, edits and errors stay in view, and approvals ask before a tool runs.
Or browse every block.
With the AI SDK
Pass useChat’s status and stop. Files arrive as FileUIPart with a data URL, and a message typed while a reply streams is queued and sent when it finishes. Send usage from the server as message metadata to fill the context ring.
Model, effort and dictation
Put the model picker, effort slider and context ring under the composer with PromptInputMeta. The ring breaks usage down into input, cache, output and reasoning, prices it, and offers to compact the conversation near the limit. Turn tools on from the + menu and show them as chips. Pass transcribe to send recordings to your own speech model, and autoSend to send as soon as the text lands.
Mentions and commands
Add a menu per trigger. @ turns a pick into a chip above the text, and / inserts the command. Arrow keys move, Enter or Tab picks and Escape closes, all without leaving the text.
Uploading files
Give upload a function that reports progress and honours the abort signal. Throw UploadError with retryable: false for errors a retry can’t fix, like a file that’s too large for the server.
Agent harness
Wrap the body in PromptInputInset and add a PromptInputFooter. Use PromptInputSelect for the mode, where it runs and which folder it works in.
Anatomy
The parts you compose, from the outside in.
| Part | Description |
|---|---|
PromptInput | The form and shared state: text, files, mentions, queue and status. |
PromptInputBody | The rounded surface that changes between compact and expanded. |
PromptInputTextarea | The field. All keyboard handling lives here. |
PromptInputTools | Left side: the + menu and tool chips. |
PromptInputActions | Right side: dictation and send. |
PromptInputSubmit | Send, stop or queue, depending on status and text. |
PromptInputAttachments | File and mention chips above the text. |
PromptInputMenu | A caret menu for one trigger character. |
PromptInputMeta | The row under the composer for the model, effort and context controls. |
PromptInputModel | One control for the model and effort, with the thinking switch. |
PromptInputInset | A muted shell around the body that holds a footer row, for agent harnesses. |
PromptInputFooter | The row inside the inset shell for location, folder and session controls. |
PromptInputSelect | A compact picker for modes, run location, folders or repositories. |
PromptInputQueue, PromptInputSuggestions, PromptInputMeter | Queued messages, starter prompts and a character meter near maxLength. |
PromptInput
Also accepts every form prop except onSubmit and defaultValue.
| Prop | Type | Default |
|---|---|---|
onSubmitReceives { text, files, mentions }. Return a promise to keep the text until it resolves. | (message: PromptMessage) => void | Promise<unknown> | – |
statusPass useChat’s status. While busy, send becomes stop or queue. | "ready" | "submitted" | "streaming" | "error" | "ready" |
onStopCalled from the stop button or Escape. | () => void | – |
onEditLastReturn the last message to load it with ↑ in an empty field. | () => string | void | – |
valueControlled text. | string | – |
defaultValueInitial text. | string | "" |
onValueChangeCalled as the text changes. | (value: string) => void | – |
submitOnWhich key sends. ⌘↵ always sends. | "enter" | "mod+enter" | "enter" |
pasteLimitCharacters before a paste becomes an attachment. | number | false | 5000 |
maxLengthHard limit, shown by PromptInputMeter. | number | – |
draftKeySaves unsent text to local storage under this key. | string | – |
globalDropAccept files dropped anywhere on the page. | boolean | false |
focusOnTypeTyping while nothing else has focus starts typing here. | boolean | false |
layoutForce a layout instead of switching. | "auto" | "compact" | "expanded" | "auto" |
acceptAccepted file types, like the input attribute. | string | – |
maxFilesMost files at once. | number | – |
maxFileSizeLargest file in bytes. | number | – |
uploadUploads each file. Throw UploadError with retryable: false for permanent failures. | (file, { signal, onProgress }) => Promise<unknown> | – |
onRejectCalled for files over a limit or of the wrong type. | (file: File, reason: string) => void | – |
disabledDisables the whole composer. | boolean | false |
| Prop | Type | Default |
|---|---|---|
triggerThe character that opens it, such as @ or /. | string | – |
items{ value, label, description?, icon?, hint?, keywords?, insert? } | PromptMenuItem[] | – |
modeMentions add a chip; text inserts insert ?? label. | "mention" | "text" | – |
labelSection label at the top of the menu. | string | – |
limitMost rows shown. | number | – |
onSelectCalled after a pick. | (item: PromptMenuItem) => void | – |
PromptInputModel
The model picker and effort slider in one control. Also takes every PromptInputEffort prop except value and onValueChange.
| Prop | Type | Default |
|---|---|---|
modelsThe first nine get 1–9 shortcuts. | { value, label }[] | – |
modelSelected model. | string | – |
onModelChangeCalled on pick. | (value: string) => void | – |
levelsEffort levels, from least to most. | { value, label }[] | – |
effortSelected effort. | string | – |
onEffortChangeCalled as the slider moves. | (value: string) => void | – |
| Prop | Type | Default |
|---|---|---|
modelsThe first nine get 1–9 shortcuts. | { value, label }[] | – |
valueSelected model. | string | – |
onValueChangeCalled on pick. | (value: string) => void | – |
| Prop | Type | Default |
|---|---|---|
levelsFrom least to most. | { value, label }[] | – |
valueSelected level. | string | – |
onValueChangeCalled as the slider moves. | (value: string) => void | – |
recommendedMarks one level as recommended. | string | – |
thinkingShows the extended-thinking switch when set. | boolean | – |
onThinkingChangeCalled from the switch. | (thinking: boolean) => void | – |
thinkingLabelSwitch label. | string | – |
descriptionHelp text in the tooltip. | string | – |
PromptInputContextUsage
A ring that fills as the context window does. Hover, click or press Enter to see the breakdown.
| Prop | Type | Default |
|---|---|---|
usedTokens used. Defaults to the total in usage. | number | – |
limitContext window size. | number | – |
usageAI SDK usage, flat or nested. Splits the bar into input, cached input, output and reasoning. | LanguageModelUsage | – |
modelModel name shown at the top. | string | – |
pricingDollars per million tokens. Prices each row and adds a total. | { input, output, cachedInput?, reasoning? } | – |
costTotal cost in dollars, when you already have it. | number | – |
onCompactShows Compact conversation from 80% full. Return a promise to show progress. The popover closes when it resolves. | () => void | Promise<unknown> | – |
limitsExtra rows, such as plan usage. | { label, used, detail? }[] | – |
limitsLabelHeading for those rows. | string | – |
| Prop | Type | Default |
|---|---|---|
transcribeSend the recording to your speech model. Without it, the browser’s speech recognition is used. | (audio: Blob) => Promise<string> | – |
shortcutStarts and finishes a recording from anywhere. | string | false | "ctrl+shift+d" |
autoSendSend as soon as the transcript lands. | boolean | false |
| Prop | Type | Default |
|---|---|---|
iconShown in the chip, and replaced by × on hover. | ReactNode | – |
childrenTool name. | string | – |
toneChip color. | "blue" | "green" | "amber" | "violet" | "pink" | "blue" |
onRemoveCalled when the chip is clicked. | () => void | – |
| Prop | Type | Default |
|---|---|---|
optionsThe choices. Descriptions show under each label. | { value, label, icon?, description? }[] | – |
valueSelected option. Leave it unset to show the placeholder. | string | – |
onValueChangeCalled on pick. | (value: string) => void | – |
labelAccessible name and tooltip, like Mode or Run on. | string | – |
placeholderShown when nothing is selected. | string | – |
iconShown when the selected option has no icon. | ReactNode | – |
headingLabel at the top of the menu. | string | – |
shortcutCycles through the options from anywhere, like alt+m. | string | – |
childrenExtra menu items below the options, like Open folder. | ReactNode | – |
| Key | Action |
|---|---|
| Enter | Sends. On touch screens it adds a line instead. |
| ShiftEnter | Adds a line. |
| ⌘Enter | Always sends, even when submitOn is mod+enter. |
| Esc | Stops a streaming reply, closes a menu, or cancels a recording. |
| ↑ | In an empty field, brings back your last message when onEditLast is set. |
| ↑↓ | Moves through an open @ or / menu. |
| EnterTab | Picks the highlighted menu row. |
| 1–9 | Picks a model while the model menu is open. |
| ←→ | Moves the effort slider. Home and End jump to the ends. |
| CtrlShiftD | Starts dictation, and inserts the transcript when pressed again. |
| AltM | Cycles a PromptInputSelect that has shortcut="alt+m", like the agent mode. |
- The field has a visible placeholder and an accessible name, and every icon button has a label and tooltip with its shortcut.
- The caret menu is a combobox with aria-activedescendant, so focus never leaves the text while moving through it.
- Uploads, rejections, queue changes and dictation are announced through a polite live region.
- The effort control is a slider with a value text, and it keeps working with arrow keys and Home and End.
- The context ring opens on hover, click, Enter or Space, its label says how much of the context is used, and the breakdown is a table with a caption.
- Inputs are at least 16px on touch screens, so iOS never zooms in on focus.
- With reduced motion, height changes, the waveform and the effort fill all settle without animating.
Built with
The free HextaUI components Prompt Input is made from. Each one installs on its own.
Code
10 files, added to components/blocks/prompt-input.