Dashral Pro is on the wayGet 30% off at launch

Dashral

Prompt Composer

Where a message to an AI assistant is written: it grows as you type, takes files, and turns send into stop while the reply streams.

$ pnpm dlx shadcn@latest add https://dashral.com/r/prompt-composer.json
Hi! Ask me about your revenue, customers or traffic.

Installation

$ pnpm dlx shadcn@latest add https://dashral.com/r/prompt-composer.json

Adds the component, the components it builds on, its npm packages and the theme tokens it uses. Using Dashral often? Add the registry to components.json once:

components.json
{
  "registries": {
    "@dashral": "https://dashral.com/r/{name}.json"
  }
}

Then add components by name:

$ pnpm dlx shadcn@latest add @dashral/prompt-composer

Usage

example.tsx
import { PromptComposer } from "@/components/dashral/prompt-composer"

<PromptComposer
  onSubmit={({ text, files }) => api.chat.send(text, files)}
  attachments={{ accept: "image/*,.pdf", maxFiles: 4 }}
/>

Examples

Attachments

From the paperclip, a pasted screenshot or files dropped on the box, checked for type, size and count.

Images, PDF or CSV, up to 4 files of 5 MB each. Paste a screenshot or drop files on the box.

Your controls in the toolbar

A model picker and a web search toggle, a character limit that counts down near the end, and a hint under it.

The assistant can make mistakes. Check numbers before you share them.

API reference

PromptComposer

PropTypeDefault
value / defaultValuestring

The text, controlled or initial. Uncontrolled, it clears itself after sending.

""
onValueChange(value: string) => void

Called with the new value.

—
onSubmit({ text, files }) => void

Called with the message (text trimmed) when it's sent.

—
status"ready" | "submitted" | "streaming"

While it's not ready, send becomes stop and Enter doesn't send.

"ready"
onStop() => void

The stop button and Escape call it.

—
submitOn"enter" | "mod-enter"

Enter sends and Shift + Enter adds a line, or only ⌘/Ctrl + Enter sends.

"enter"
attachmentsboolean | { accept?, maxFiles?, maxSize? }

Files from the paperclip, a paste or a drop; true takes any file.

—
files / defaultFiles / onFilesChangeFile[]

The attached files, controlled or initial.

[]
maxLengthnumber

A count shows near it; past it the message can't be sent.

—
actionsReactNode

Your controls in the toolbar, after the paperclip: a model picker, a toggle.

—
placeholderstring"Ask anything…"
disabled / required / autoFocus / id / name—
labelReactNode

Shown above, linked to the control.

—
descriptionReactNode

A hint under the control.

—
errorboolean | ReactNode

true shows the error style; a message also shows under the control.

—
labelActionReactNode

At the end of the label's row, e.g. a “Forgot password?” link or the current value.

—
withAsteriskboolean

Marks the label with an asterisk, e.g. alongside required.

false
classNames{ root, label, description, error, composer, textarea, files, toolbar, submit }

Classes for each part.

—
classNamestring

Classes for the composer's box.

—

Keyboard

KeysAction
EnterSend (with submitOn "enter")
Shift + EnterA new line
⌘/Ctrl + EnterSend, with either submitOn
EscapeStop the reply while it streams
TabThe paperclip, your controls, then send or stop

Accessibility

  • With label, description or error, the id, aria-describedby and aria-invalid are wired up for you.
  • Without a label the text box is named "Message"; the buttons are named Send message, Stop generating and Attach files.
  • Enter waits while a word is still being composed (Japanese, Chinese or Korean input), so it doesn't send half a word.
  • Attached files are a list, each with a remove button that names the file; refused files are listed in an alert with the reason.
  • Near the limit, a live region says how many characters are left, without reading every keystroke.

Motion

  • The border and focus ring ease over --duration-fast; the count fades in near the limit. A drop shows its message at once.