Skip to content

Prompt Input

Compose a message with attachments and a send button that becomes a stop control.

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use it below a Chat Message List for anything someone writes to an assistant. It handles the keyboard rules people expect from a chat composer, attachments by picker, paste and drop, and the switch from send to stop while a reply runs. For a plain multi-line field in a form, use a Textarea.

import { PromptInput } from '@youj/react'

<PromptInput status={status} onStop={stop} onSubmit={({ text, attachments }) => send(text, attachments)} />

onSubmit gets the trimmed text and the attachments. Uncontrolled, the field clears itself after sending. Control it with value and onValueChange to fill it from elsewhere, like a suggestion, and textareaRef to focus it.

Enter sends and Shift+Enter breaks the line, and neither cuts an IME composition short. On touch keyboards Enter is a line break and the button sends. The field grows with the message up to 40% of the viewport, then scrolls.

Reference

PropsonSubmit, value, defaultValue… (23)

Props

PromptInput props
PropType
valuestring–
defaultValuestring

default ''

''
onValueChange(value: string) => void–
onSubmitRequired(message: PromptSubmission) => void

Called with the trimmed text and the attachments. Uncontrolled, the text then clears itself.

–
statusPromptStatus
'idle''submitted''streaming'

'submitted' and 'streaming' turn send into stop.

default 'idle'

'idle'
onStop() => void–
placeholderstring

default "Ask anything…"

"Ask anything…"
labelstring

The text field's name for assistive tech.

default "Message"

"Message"
disabledboolean

default false

false
attachmentsPromptAttachment[]

default []

[]
onAttach(files: File[]) => void

Enables attaching: the paperclip, paste and drop. Receives the files.

–
onRemoveAttachment(id: string) => void–
acceptstring

Which files the picker offers and paste or drop accept, as in <input accept>.

–
toolbarReactNode

Controls at the start of the bottom bar: a model select, tool toggles.

–
counterReactNode

Replaces the built-in character counter, e.g. <PromptInputCounter value={tokens} max={8000} unit="tokens" />.

–
maxLengthnumber

A character limit: shows a counter near it and blocks sending past it.

–
autoFocusboolean–
textareaRefRef<HTMLTextAreaElement>

The textarea, e.g. to focus it after picking a suggestion.

–
Other props pass through: extends ComponentProps<'form'>.

PromptInputCounter

PromptInputCounter props
PropType
valueRequirednumber–
maxRequirednumber–
unitstring

What's counted, for the announcement.

default "characters"

"characters"
fromnumber

Show from this share of max.

default 0.8

0.8
idstring–
MotionSend to stop, reversal, attachment enters… · reduced motion

Motion

Motion tokens
MomentTokens
Send to stopIconSwap morph: the arrow’s strokes bend into the square, and back
ease.emphasized

cubic-bezier(0.6, 0, 0.1, 1) · deliberate + fast: 700ms

cubic-bezier(0.6, 0, 0.1, 1)deliberate + fast: 700ms
Reversalstopping mid-morph turns back from where it is
no token

length × distance left

length × distance left
Attachment entersfades and drops 6px (Presence List)
duration.baseease.enter

220ms · cubic-bezier(0.16, 1, 0.3, 1)

220mscubic-bezier(0.16, 1, 0.3, 1)
Attachment leavesfades in place, then the rest close the gap
duration.fastease.exit

140ms · cubic-bezier(0.4, 0, 1, 1)

140mscubic-bezier(0.4, 0, 1, 1)
Chips make room
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Counterfades in near the limit
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Send and stop swap instantly, and chips appear and disappear in place.

Reduced motion

The arrow swaps to the square without morphing, and attachment chips appear and leave at once. Everything else, the keyboard rules and the counter, is unchanged.

AccessibilityARIA, focus, announcements

Accessibility

  • The field is named by label (default "Message"). With maxLength, it's described by the counter and set aria-invalid past the limit.
  • The counter is announced only in the last stretch (under 10% left) and when over, not on every keystroke.
  • Send is named "Send message", and "Stop generating" while busy, with a tooltip. It stays focusable while it can't act.
  • The paperclip is named "Attach files". Attachments are a list named "Attachments", and each remove button is named after its file. Removing one moves focus to its neighbor, never to the page.
  • disabled grays the whole frame and blocks typing, attaching and sending.