elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Chat / PromptInput

PromptInput

A growing message composer with attachments, tools, a model picker and a send button that stops a streaming reply.

Chat

With MessageList: send, watch the status go submitted, streaming, ready, and stop a reply mid-stream.

Hi! Ask me anything, attach a file, or drop one on the box.

Drop files to attach
status: ready · stops: 0 · model: claude-sonnet · search: false
Code
<MessageList busy={state.status === "streaming"}>
  <Message e:for={m of state.messages} e:key={(m) => m.id} from={m.role}>...</Message>
</MessageList>
<PromptInput value={state.text} files={state.files} multiple onSubmit={(m) => send(state, m)}>
  <PromptInputTools>
    <PromptInputActionMenu>
      <PromptInputActionAddAttachments />
      <PromptInputActionAddScreenshot />
    </PromptInputActionMenu>
    <PromptInputButton toggle pressed={state.search} tooltip="Search the web"><Icon name="globe" /> Search</PromptInputButton>
    <PromptInputSelect value={state.model} options={MODELS} />
  </PromptInputTools>
  <PromptInputSubmit status={state.status} onStop={() => stop(state)} />
</PromptInput>

Usage

import { PromptInput, PromptInputTools, PromptInputButton, PromptInputSubmit, PromptInputActionMenu, PromptInputActionAddAttachments, PromptInputSelect } from "@elements/ui/prompt-input";

<PromptInput value={state.text} files={state.files} onSubmit={(m) => send(chat, m)}>
  <PromptInputTools>
    <PromptInputActionMenu>
      <PromptInputActionAddAttachments />
    </PromptInputActionMenu>
    <PromptInputButton toggle pressed={state.search} tooltip="Search the web" shortcut="⌘K">
      <Icon name="globe" /> Search
    </PromptInputButton>
    <PromptInputSelect value={state.model} options={MODELS} />
  </PromptInputTools>
  <PromptInputSubmit status={chat.status} onStop={() => stop(chat)} />
</PromptInput>

Examples

Files

Pick, paste or drop files. accept, maxFiles and maxFileSize refuse what does not fit and report it through onError.

Drop files to attach
error: none submits: 0 · last: none
Code
<PromptInput value={state.text} files={state.files}
             accept="image/*,.md" multiple maxFiles={3} maxFileSize={1_000_000}
             onError={(err) => state.error = err.message}
             onSubmit={(m) => save(m)}>
  <PromptInputTools>
    <PromptInputButton icon tooltip="Attach files" onclick={(e) => openPromptInputFileDialog(e.currentTarget)}>
      <Icon name="paperclip" />
    </PromptInputButton>
  </PromptInputTools>
  <PromptInputSubmit />
</PromptInput>

Tools

The + menu, a toggle with a tooltip and shortcut, dictation and a model picker.

Drop files to attach
search: false · model: claude-opus · template picks: 0 · screenshot picks: 0
Code
<PromptInputTools>
  <PromptInputActionMenu>
    <PromptInputActionAddAttachments />
    <PromptInputActionAddScreenshot />
    <!-- e.preventDefault() in onSelect skips the capture. -->
    <PromptInputActionAddScreenshot label="Screenshot (held back)" onSelect={(e) => e.preventDefault()} />
    <PromptInputActionMenuItem onSelect={() => insertTemplate()}><Icon name="sparkles" /> Use a template</PromptInputActionMenuItem>
  </PromptInputActionMenu>
  <PromptInputButton toggle pressed={state.search} tooltip="Search the web" shortcut="⌘K"><Icon name="globe" /> Search</PromptInputButton>
  <PromptInputSpeechButton />
  <PromptInputSelect value={state.model} options={MODELS} />
</PromptInputTools>

Styled model picker

A Select composes into PromptInputTools in place of the native PromptInputSelect. The prompt-input-select class gives its trigger the ghost look of the other tools; its popup opens from inside the form, and choosing a model never sends the prompt.

Claude Opus
Claude Sonnet
Claude Haiku
Drop files to attach
model: claude-opus · sent: 0
Code
<!-- Select instead of the native PromptInputSelect: prompt-input-select
     gives its trigger the quiet ghost look; the popup opens from the form. -->
<PromptInput onSubmit={(m) => send(m)}>
  <PromptInputTools>
    <Select value={state.model} items={MODELS} label="Model" size="sm" position="popper" side="top"
            class="prompt-input-select" />
  </PromptInputTools>
  <PromptInputSubmit />
</PromptInput>

Async submit

A promise from onSubmit marks the form pending; it clears on resolve and keeps the text on reject. Type fail to see a rejection.

Drop files to attach
result: none
Code
// Resolve and it clears; reject and the text stays for a retry.
<PromptInput value={state.text} onSubmit={(m) => saveDraft(m)}>
  <PromptInputSubmit />
</PromptInput>

Submit button

PromptInputSubmit takes a size and an onclick that runs before the submit; e.preventDefault() in it cancels the send. Type draft to see it held back.

Drop files to attach
clicks: 0 · sent: 0
Drop files to attach
Code
<PromptInput value={state.text} onSubmit={(m) => send(m)}>
  <PromptInputSubmit size="md" onclick={(e) => { track(); if (isDraft()) e.preventDefault(); }} />
</PromptInput>

<!-- Slotted content replaces the status icon. -->
<PromptInputSubmit label="Send"><Icon name="arrow-up" /></PromptInputSubmit>

Cursor style

An @ hover card in the header searches files; picking one adds it as a referenced source and drops it from the list. The buttons below drive the input from outside.

Drop files to attach
status: ready · sources: none · files: 0
Code
<PromptInput value={state.text} sources={state.sources} onSubmit={(m) => send(m)}>
  <HoverCard slot="header" side="top" align="start">
    <PromptInputButton slot="trigger" icon label="Add context"><Icon name="at-sign" /></PromptInputButton>
    <Command label="Files">
      <CommandInput placeholder="Search files..." />
      <CommandList>
        <CommandGroup heading="Files">
          <CommandItem e:for={p of state.files} onSelect={() => addContext(state, p)}>{p}</CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  </HoverCard>
  <PromptInputTools>
    <PromptInputSelect value={state.model} options={MODELS} />
  </PromptInputTools>
  <PromptInputSubmit status={state.status} />
</PromptInput>

removePromptInputSource(el, id); clearPromptInputSources(el);
addPromptInputFiles(el, [file]); removePromptInputFile(el, id); clearPromptInputFiles(el); clearPromptInput(el);

Sources and suggestions

Referenced sources ride along as chips. Tabs parts lay out suggestions in a hover card.

Drop files to attach
last: none
Code
<PromptInput value={state.text} sources={state.sources} onSubmit={(m) => ask(m.text, m.sources)}>
  <PromptInputTools>
    <HoverCard openDelay={0} closeDelay={0} align="start">
      <PromptInputButton slot="trigger" icon label="Mention a source"><Icon name="at-sign" /></PromptInputButton>
      <PromptInputTabsList>
        <PromptInputTab>
          <PromptInputTabLabel>Recently used</PromptInputTabLabel>
          <PromptInputTabBody>
            <PromptInputTabItem><Icon name="globe" /> Elements manual</PromptInputTabItem>
          </PromptInputTabBody>
        </PromptInputTab>
      </PromptInputTabsList>
    </HoverCard>
  </PromptInputTools>
  <PromptInputSubmit />
</PromptInput>

Options

globalDrop takes a file dropped anywhere on the page; submitOnEnter off makes Enter a newline; allowEmpty sends with nothing typed; autofocus focuses on mount.

Drop files to attach
Drop files to attach
sent: 0
Drop files to attach
Drop files to attach
Code
<!-- Drop a file anywhere on the page, not only on the box. -->
<PromptInput value={state.text} files={state.files} globalDrop multiple>...</PromptInput>

<!-- Enter breaks a line; only the button sends. -->
<PromptInput value={state.text} submitOnEnter={false}>...</PromptInput>

<!-- Send with nothing typed (a "continue" prompt). -->
<PromptInput value={state.text} allowEmpty onSubmit={() => next()}>...</PromptInput>

<!-- Focus the textarea when it mounts. -->
<PromptInput value={state.text} autofocus>...</PromptInput>

Slash commands

Command in the header slot: search, arrow and Enter to pick a command into the prompt.

Slash commands
No commands.
Commands
/summarize
/translate
/explain
Drop files to attach
picked: none
Code
<PromptInput value={state.text}>
  <Command slot="header" label="Slash commands" class="prompt-command">
    <CommandInput placeholder="Search commands..." />
    <CommandList>
      <CommandEmpty>No commands.</CommandEmpty>
      <CommandGroup heading="Commands">
        <CommandItem onSelect={() => insert("/summarize ")}>/summarize</CommandItem>
      </CommandGroup>
    </CommandList>
  </Command>
  <PromptInputTools>
    <PromptInputActionMenu icon="paperclip" label="Attach">
      <PromptInputActionAddAttachments />
    </PromptInputActionMenu>
  </PromptInputTools>
  <PromptInputSubmit />
</PromptInput>

Textarea props

textareaAttrs reach the textarea (maxlength, aria-describedby); onTextareaKeyDown runs first and can cancel the built-in Enter. Here Enter adds a line, Cmd or Ctrl+Enter sends, and ArrowUp in an empty box recalls the last message.

0 / 280
Drop files to attach
sent: 0 · last: none
Code
<PromptInput textareaAttrs={{ maxlength: 280, "aria-describedby": "count" }}
             onTextareaKeyDown={(e) => onKey(e)}>   <!-- preventDefault skips Enter to send -->
  ...
</PromptInput>
<span id="count">{text.length} / 280</span>

Disabled

A disabled fieldset disables the textarea and every control in it.

Drop files to attach
Code
<PromptInput disabled placeholder="Sign in to send messages">...</PromptInput>
Reference PromptInput in the manual Every part, attribute, slot, key and data attribute.
Prev MessageList
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026