PromptInput
A composer for AI chat and standalone "Ask anything" boxes — auto-growing textarea, toolbar slots, and a status-aware submit button.1(function PromptInputPreview() {2 const [status, setStatus] = React.useState("idle");3 const timerRef = React.useRef(null);45 React.useEffect(() => () => clearTimeout(timerRef.current), []);67 return (8 <div style={{ width: 420 }}>9 <PromptInput10 status={status}11 onSubmit={(value, event) => {12 event.currentTarget.reset();13 setStatus("streaming");14 timerRef.current = setTimeout(() => setStatus("idle"), 2500);15 }}
Anatomy
Import and assemble the composer. The root is a form that owns submit semantics: Enter submits, Shift+Enter inserts a newline, and IME composition is respected.
1import { PromptInput } from '@raystack/apsara'23<PromptInput onSubmit={send} onStop={stop} status={status}>4 <PromptInput.Header>{/* attachment previews */}</PromptInput.Header>5 <PromptInput.Textarea placeholder="Reply…" />6 <PromptInput.Footer>7 <PromptInput.Button>Skills</PromptInput.Button>8 <PromptInput.Submit />9 </PromptInput.Footer>10</PromptInput>
PromptInput is purely presentational: it never talks to a model or manages a
request. Pass the lifecycle in through status and react to onSubmit /
onStop — it works with the AI SDK's useChat, a custom SSE client, or
anything else.
API Reference
Root
The <form> that holds the value and submit semantics.
Prop
Type
Textarea
The text field, built on TextArea variant="borderless". It grows with its
content up to a max-height cap (override with
--prompt-input-textarea-max-height), then scrolls.
Prop
Type
Header
A slot row above the textarea, usually for Chat.Attachment previews. Hidden
while empty.
Footer
The bottom toolbar row. Hidden while empty.
Button
A small neutral-ghost Button for the toolbar — attach, skills, model pickers
and similar consumer-owned controls.
Prop
Type
Submit
The trailing send button. It renders ↑ when idle, disables itself while the
input is empty, shows a spinner while status="submitted", and flips to a
stop square that calls onStop while status="streaming".
Prop
Type
Examples
Attachments in the header
Attachment previews are presentational (Chat.Attachment); file picking,
drag-drop and uploads belong to your app.
1<div style={{ width: 420 }}>2 <PromptInput onSubmit={(value) => console.log(value)}>3 <PromptInput.Header>4 <Chat.Attachment5 title="design-spec.pdf"6 description="1.2 MB"7 onRemove={() => {}}8 />9 <Chat.Attachment10 title="screenshot.png"11 state="uploading"12 description="Uploading…"13 />14 </PromptInput.Header>15 <PromptInput.Textarea placeholder="Reply…" />
Controlled value
Control value to sync the draft elsewhere — persistence, slash-command
menus, mention pickers.
1(function ControlledPromptInput() {2 const [value, setValue] = React.useState("");34 return (5 <Flex direction="column" gap={4} style={{ width: 420 }}>6 <PromptInput7 value={value}8 onValueChange={setValue}9 onSubmit={() => setValue("")}10 >11 <PromptInput.Textarea placeholder="Write a message…" />12 <PromptInput.Footer>13 <PromptInput.Submit />14 </PromptInput.Footer>15 </PromptInput>
Disabled
1<div style={{ width: 420 }}>2 <PromptInput disabled>3 <PromptInput.Textarea placeholder="Read-only conversation" />4 <PromptInput.Footer>5 <PromptInput.Button>Skills</PromptInput.Button>6 <PromptInput.Submit />7 </PromptInput.Footer>8 </PromptInput>9</div>
Accessibility
- The root is a native
<form>;PromptInput.Submitis a real submit button, so assistive tech announces the submit affordance for free. - Enter submits and Shift+Enter inserts a newline. During IME composition Enter confirms the composition instead of submitting.
- While a response is in flight the submit button becomes
type="button"with an updated accessible name ("Stop response") so it can never resubmit the form. - The composer frame carries the focus treatment (
:focus-within), while the borderless textarea suppresses its own duplicate focus ring.