Prompt Input.
Say it, send it, stop it.
The message composer: a textarea that grows with its content, a row for tools such as a model picker, and one button that sends, then stops the reply while it is in progress.
Make it yours
Preparing the example…
Composition
AnatomyRoot owns the value (controlled or not) and the request status. While submitted or streaming, Enter does not send and Submit becomes Stop; typing the next message is still allowed.
PromptInput.Root (form; value, onSubmit, status, onStop)
├── PromptInput.Textarea — autosizes; Enter sends
└── PromptInput.Footer
├── PromptInput.Tools — Select, Toggle, …
└── PromptInput.Submit — Send ↔ StopA place to start
UsageImport the stylesheet once at your application root. This complete example reflects the properties selected above.
Loading example…
Details that make a difference
Examples & statesThe finer details
API referenceGenerated from Dixy’s local TypeScript interfaces. Native HTML attributes and refs are forwarded. The table covers Dixy’s own options.
PromptInputRoot
| Prop | Type / description | Default |
|---|---|---|
value | stringControlled text. Pair with `onValueChange`. | — |
defaultValue | stringInitial text when uncontrolled. | "" |
onValueChange | ((value: string) => void)Called on every edit. | — |
onSubmit | ((text: string) => void)Called with the trimmed text when the user sends. Uncontrolled inputs clear themselves afterwards. Not called for empty text or while busy. | — |
status | ChatStatusRequest state. While submitted or streaming, the send button becomes a stop button and Enter does not send. | ready |
onStop | (() => void)Called when the user stops a reply in progress. | — |
disabled | booleanDisable typing and sending. | false |
PromptInputTextarea
| Prop | Type / description | Default |
|---|---|---|
aria-label | stringAccessible name. | Message |
maxHeight | numberTallest the field grows before it scrolls, in pixels. | 200 |
PromptInputSubmit
| Prop | Type / description | Default |
|---|---|---|
sendLabel | stringAccessible name while ready. | Send message |
stopLabel | stringAccessible name while a reply is in progress. | Stop generating |
children | ReactNode | ((stopping: boolean) => ReactNode)Replace the icons. Receives whether the button currently stops. | — |
Built from the same foundations
Styling tokensChange Theme settings above to compare appearance, accent, neutral, radius, spacing density, motion and direction. Override component aliases on a Theme root so custom properties follow portaled content too. The wrapper merges className, style, refs and render composition with its owning primitive.
| Token | Purpose |
|---|---|
--dixy-radius-card | The component-specific starting point. |
--dixy-font-label-md | Control label font and line height; body roles remain readable in text inputs. |
--dixy-color-background-selected | Selected surfaces use semantic color across appearances. |
--dixy-color-border-focus | Focus indication remains visible independently of shadows. |
--dixy-spacing-inset-sm | Density-aware component padding. |
--dixy-motion-transition-feedback | Short state feedback, reduced by the inherited motion setting. |
Consider everyone
AccessibilitySend and Stop are one button that changes its name, so focus survives the switch. With empty input the button is aria-disabled but still focusable, so focus is never dropped to the page after sending. The textarea has an accessible name, not just a placeholder.
Target: WCAG 2.2 AA. Automated tests cover selected behavior; manual screen-reader, contrast, and zoom validation remain part of the release checklist.
Build it. Then understand it.
isComposing is the difference between a Japanese user confirming a word and sending half a sentence. Safari ends composition before keydown, so keyCode 229 is checked as well.
--dixy-radius-card