Prompt

Composes chat input with attachments and pickers, switching Send to Stop while the assistant works.

import { Prompt } from "@usefragments/ui";Source

Preview

const send = (message: string) => {};

<Prompt onSubmit={send}>
  <Prompt.Textarea />
  <Prompt.Toolbar>
    <Prompt.Attach />

Usage

Use when

  • Chat and AI assistant interfaces
  • Multi-line input with a submit action
  • Toolbars carrying attachments or a model picker

Avoid when

  • Single-line text (use Input)
  • Multi-line with no toolbar (use Textarea)
  • Search-only fields (use Input with the search variant)

Implementation notes

  • Always pass onSubmit; pair working with onStop so Send becomes Stop
  • pending while the message is on its way; working while the assistant responds
  • The text stays editable while working, so the next message can be drafted
  • Prompt.Picker chooses one of a few options (a model, a mode) from a menu; its name is read before the value
  • Prompt.Submit is the one accent action; a menu on it opens by right-click, ArrowDown or long-press, never by a plain click
  • Wire onFiles once; the attach button, paste and drop all route through it
  • Render Prompt.Attachments above Prompt.Textarea; they are part of the message
  • invalid with errorMessage says why the message can't be sent

Installation

pnpm add @usefragments/ui

The npm package stays managed by your package manager. Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Prompt once, then compose its parts with dot notation.

  • Prompt.Textarea
  • Prompt.Toolbar
  • Prompt.Actions
  • Prompt.Info
  • Prompt.Picker
  • Prompt.Attach
  • Prompt.Attachments
  • Prompt.Submit

The text area, attach and Send.

const send = (message: string) => {};

<Prompt onSubmit={send}>
  <Prompt.Textarea />
  <Prompt.Toolbar>
    <Prompt.Attach />
    <Prompt.Actions>
      <Prompt.Submit />
    </Prompt.Actions>
  </Prompt.Toolbar>
</Prompt>

Examples

With Picker

A model picker and a token count beside Send.

const models = [{ value: "fast", label: "Fast" }, { value: "thorough", label: "Thorough" }];
const [model, setModel] = React.useState("fast");
const send = (message: string) => {};

<Prompt onSubmit={send}>
  <Prompt.Textarea />

While Working

Send becomes Stop; the text stays editable.

const [responding, setResponding] = React.useState(true);
const send = (message: string) => setResponding(true);
const stop = () => setResponding(false);

<Prompt working={responding} onStop={stop} onSubmit={send}>
  <Prompt.Textarea />

With Attachments

Attach, paste or drop files; chips sit above the text.

import type { PromptAttachment } from "@usefragments/ui";

const [files, setFiles] = React.useState<PromptAttachment[]>([{ id: "notes", name: "notes.txt" }]);
const addFiles = (selected: File[]) => setFiles((current) => [...current, ...selected.map((file, index) => ({ id: `${file.name}-${index}`, name: file.name }))]);
const removeFile = (id: string) => setFiles((current) => current.filter((file) => file.id !== id));
const send = (message: string) => {};

Invalid

Says why the message can't be sent.

const send = (message: string) => {};

<Prompt invalid errorMessage="Messages can be up to 4,000 characters." onSubmit={send}>
  <Prompt.Textarea />
</Prompt>

Disabled

Nothing can be typed or sent.

<Prompt disabled placeholder="Connect a repository to ask">
  <Prompt.Textarea />
</Prompt>

API reference

Component props
Prop
Type
Default
Description
acceptstring | undefinedNot setFile types the attach, paste and drop paths take
Actions.childrenReact.ReactNodeNot set—
Attach.acceptstring | undefinedNot setOverrides the prompt's `accept`.
Attach.aria-labelstring | undefinedNot setAccessible name.
Attach.childrenReact.ReactNodeNot setCustom glyph. Defaults to a plus.
Attach.classNamestring | undefinedNot set—
Attach.disabledboolean | undefinedNot set—
Attach.multipleboolean | undefinedNot set—
Attachments.classNamestring | undefinedNot set—
Attachments.itemsRequiredPromptAttachment[]Not set—
Attachments.onRemove((id: string) => void) | undefinedNot setOmit to render the strip read-only.
childrenRequiredReact.ReactNodeNot setPrompt parts: Attachments, Textarea, Toolbar
defaultValuestring | undefinedInitial text when uncontrolled
disabledboolean | undefinedfalseNothing can be typed or sent
errorMessageReact.ReactNodeNot setWhy the message can't be sent, shown under the text
Info.childrenRequiredReact.ReactNodeNot set—
invalidboolean | undefinedfalseThe message can't be sent as written; pair with errorMessage
maxRowsnumber | undefined8Rows before the text area scrolls
minRowsnumber | undefined1Rows before the text area grows
onFiles((files: File[]) => void) | undefinedNot setReceives files from Attach, paste and drop
onStop(() => void) | undefinedNot setStop the response while working
onSubmit((value: string) => void) | undefinedNot setCalled with the text when the message is sent
onValueChange((value: string) => void) | undefinedNot setCalled with the new text on every change
pendingboolean | undefinedfalseSending: Send shows a spinner after a second and Enter waits
Picker.aria-labelRequiredstringNot setWhat the choice is about (Model, Agent); read before the choice.
Picker.classNamestring | undefinedNot set—
Picker.defaultValuestring | undefinedNot setInitial value when uncontrolled.
Picker.disabledboolean | undefinedNot set—
Picker.iconReact.ReactNodeNot setLeading glyph.
Picker.onValueChange((value: string) => void) | undefinedNot set—
Picker.optionsRequiredPromptPickerOption[]Not set—
Picker.placeholderstring | undefinedNot setShown before anything is chosen.
Picker.valuestring | undefinedNot setControlled value.
placeholderstring | undefinedAsk, search or chat…Hint in the empty text area
readOnlyboolean | undefinedfalseThe text can be read and selected, not changed or sent
Submit.aria-labelstring | undefinedNot setAccessible name of the send action.
Submit.childrenReact.ReactNodeNot setCustom glyph. Defaults to an up arrow.
Submit.classNamestring | undefinedNot set—
Submit.menuReact.ReactNodeNot setMenu items for other ways to send (queue, send later). A click still sends; right-click, a long press, ArrowDown, Shift+F10 or the context-menu key opens the menu.
Submit.stopLabelstring | undefinedNot setAccessible name of the stop action while working.
submitOnEnterboolean | undefinedtrueEnter sends, Shift+Enter breaks the line
Textarea.placeholderstring | undefinedNot setOverride the prompt's placeholder.
Toolbar.childrenRequiredReact.ReactNodeNot set—
valuestring | undefinedNot setControlled text
workingboolean | undefinedfalseThe assistant is responding: the work wash plays, Send becomes Stop when onStop is set, the text stays editable

Accessibility

  • Enter sends, Shift+Enter breaks the line; Enter waits while pending or working
  • The frame draws one focus ring for the text area
  • Send and Stop are labelled buttons; the picker reads its name before its value
  • The error message is linked with aria-describedby and the text area reports aria-invalid
  • Working sets aria-busy; the drop hint is words, not only a ring
  • InputalternativeUse Input for simple single-line text input
  • TextareaalternativeUse Textarea for multi-line without toolbar

Next steps