InputMessage
A message composer with an expanding text area, attachments, and a send action.
Installation
Import from the Reflow workspace package.
Playground
Open demoimport { InputMessage, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage attachments={true} disabled={false} size="default" history={['Make my input box feel less stiff']} minRows={2} onSend={sendMessage} placeholderSuggestion="Why is every other input box so stiff?" suggestions={["What is Reflow about?","How does Reflow tune the springs behind these animations?","Install the InputMessage component in my project","Draft a short welcome message for my app"]} />
</>
)
}Basic
import { InputMessage, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage attachments={false} onSend={sendMessage} />
</>
)
}Suggestions
Press Tab in the empty composer to accept the ghost prompt, or use ArrowDown and Enter to pick one from the list.
import { InputMessage, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage placeholderSuggestion="Why is every other input box so stiff?" suggestions={prompts} onSend={sendMessage} />
</>
)
}Attachments
Use the attachment button or drop files onto the composer.
import { InputMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const send = (text: string, files: readonly File[]) => { console.log(text, files) }
return () => (
<>
<InputMessage attachments maxFiles={3} onSend={({ text, files }) => send(text, files)} />
</>
)
}Queued messages
import { InputMessage, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
const stopResponse = () => { console.log('Response stopped') }
return () => (
<>
<InputMessage status={status} onStop={stopResponse} transitionMessages onSend={sendMessage} />
</>
)
}Left Slot Only
import { InputMessage, Button, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage attachments={false} actions={<Button variant="ghost">Attach context</Button>} onSend={sendMessage} />
</>
)
}Right Slot Only
import { InputMessage, Dropdown, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage attachments={false} rightActions={<Dropdown label="Sonnet 4.6" items={models} />} onSend={sendMessage} />
</>
)
}Suggested messages
Use ArrowUp and ArrowDown to choose a suggestion, then Enter to accept it.
import { InputMessage, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage suggestions={['Summarize this document', 'Plan my next project']} onSend={sendMessage} />
</>
)
}Message history
Press ArrowUp in an empty composer to recall a previous message.
import { InputMessage, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
let plan = 'free'
const select = (value: string) => { plan = value; handle.update() }
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage history={['Draft a plan', 'Review the design']} onSend={sendMessage} />
</>
)
}Custom actions
import { InputMessage, Button, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage actions={<Button variant="ghost" size="compact">Attach context</Button>}
onSend={sendMessage} />
</>
)
}Send Handler
import { InputMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const saveMessage = async (text: string, files: readonly File[]) => { console.log(text, files) }
return () => (
<>
<InputMessage onSend={async ({ text, files }) => {
await saveMessage(text, files)
}} />
</>
)
}Disabled
import { InputMessage, type ComposerMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const sendMessage = ({ text, files }: ComposerMessage) => { console.log(text, files) }
return () => (
<>
<InputMessage disabled onSend={sendMessage} />
</>
)
}API Reference
Public props for the Reflow implementation. Native controls also accept their standard HTML attributes and Remix event mixes.
InputMessage
| Prop | Type | Default | Description |
|---|---|---|---|
transitionMessages | boolean | — | Animates a queued message into its matching sent-message bubble when view transitions are supported. |
queue | readonly ComposerQueuedMessage[] | — | Controlled messages waiting for the active response to finish. |
onQueueChange | ((queue: ComposerQueuedMessage[]) => void) | — | Receives the updated queue when messages are added, edited, reordered, or removed. |
showQueue | boolean | — | Displays queued messages above the composer. |
value | string | — | Current value when controlled by the parent. |
onValueChange | ((value: string) => void) | — | Called when the selected value changes. |
files | readonly File[] | — | Controlled selected attachments. |
onFilesChange | ((files: File[]) => void) | — | Receives attachments after adding or removing files. |
accept | string | — | Accepted file types passed to the file input. |
filePreviewSize | number | 80 | Size of image attachment previews in pixels. |
minRows | number | 1 | Minimum visible textarea rows. |
maxRows | number | 8 | Maximum visible rows before the composer scrolls. |
size | "default" | "compact" | — | Control density or icon-button dimensions. |
history | readonly string[] | [] | Previous messages recalled with the arrow keys. |
suggestions | readonly string[] | [] | Suggested messages presented while the composer is empty. |
status | "idle" | "streaming" | — | Whether the assistant is idle or streaming. Submissions during streaming join the queue. |
onStop | (() => void) | — | Called when the user stops a streaming response. |
onSend* | (message: ComposerMessage) => void | Promise<void> | — | Receives the submitted message and selected files. |
label | string | Message | Accessible name or visible label for the control. |
placeholder | string | Type a message… | Hint shown while the value is empty. |
placeholderSuggestion | string | — | Suggestion accepted with Tab while the composer is empty. |
defaultValue | string | '' | Initial value for an uncontrolled component. |
disabled | boolean | — | Prevents interaction and conveys the disabled state. |
attachments | boolean | true | Message attachments, or permission to attach files in a composer. |
maxFiles | number | 5 | Maximum number of attachments. |
actions | RemixNode | — | Actions displayed beside the message or composer. |
ComposerMessage
| Prop | Type | Default | Description |
|---|---|---|---|
queuedId | string | — | Identifier of the queued message being sent, when applicable. |
text* | string | — | Trimmed text of the submitted message. |
files* | readonly File[] | — | Files attached to the message. |
ComposerQueuedMessage
| Prop | Type | Default | Description |
|---|---|---|---|
id* | string | — | Stable identifier used to edit, remove, reorder, and animate the queued message. |
queuedId | string | — | Identifier of the queued message being sent, when applicable. |
text* | string | — | Text waiting to be submitted. |
files* | readonly File[] | — | Files attached to the queued message. |