Continue the conversation
Paste or drop files, or choose them from your device. Up to four files, 10 MB each. Sending is local to this example.
Composes a compact growing draft with validated file previews, paste and drop, screenshot capture, and retained drafts on failure.
Paste or drop files, or choose them from your device. Up to four files, 10 MB each. Sending is local to this example.
Three ways in. They share one source, so the pixels match whichever you pick.
npm install @noorddev/vlak-react
// once, next to your app's root
import "@noorddev/vlak-react/css";
import { Button, MessageComposer, useMessageComposer, type ComposedMessage } from "@noorddev/vlak-react";Precompiled React and one stylesheet. No compiler to configure. Per-component imports work too: @noorddev/vlak-react/components/message-composer.
npx @noorddev/vlak-cli add message-composer
The StyleX leaf lands in components/vlak/ with its dependencies, for your own compiler to own. See StyleX.
npx shadcn add https://vlak.dev/r/message-composer.json
The same registry item, installed by shadcn's CLI.
<link rel="stylesheet" href="node_modules/@noorddev/vlak/css/vlak.css" />
No React. Link vlak.css and use the markup and classes below.
"use client";
import { Button, MessageComposer, useMessageComposer, type ComposedMessage } from "@noorddev/vlak-react";
function ClearDraft() {
const draft = useMessageComposer();
return <Button variant="subtle" size="sm" disabled={draft.disabled || !draft.value}
onClick={() => { draft.setValue(""); draft.focus(); }}>Clear draft</Button>;
}
export function ProjectComposer({ onSend, generating = false, onStop }: {
onSend: (message: ComposedMessage) => void | Promise<void>;
generating?: boolean;
onStop?: () => void;
}) {
return <MessageComposer compact maxRows={6} sendOnEnter onSend={onSend}
generating={generating} onStop={onStop} tools={<ClearDraft />}
textareaProps={{ "aria-label": "Ask about the project", autoComplete: "off" }}
allowAttachments accept="image/*,.pdf" maxFiles={4}
maxFileSize={10 * 1024 * 1024} allowScreenshot />;
}A message draft with attachments, IME-safe shortcuts, and retained text after send failures.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | ||
defaultValue | string | "" | |
onValueChange | (text: string) => void | ||
onSendrequired | (message: ComposedMessage) => void | Promise<void> | ||
label | string | "Message" | |
placeholder | string | "Write a message…" | |
disabled | boolean | false | |
allowAttachments | boolean | false | |
accept | string | ||
files | File[] | Controlled file selection, independent of the text draft. | |
defaultFiles | File[] | [] | |
onFilesChange | (files: File[]) => void | ||
multiple | boolean | true | |
maxFiles | number | ||
maxFileSize | number | ||
onAttachmentError | (rejections: MessageAttachmentRejection[]) => void | ||
globalDrop | boolean | false | Also accept file drops outside this composer. Enable on one composer per page. |
allowScreenshot | boolean | false | Shows a user-activated browser screen capture action when available. |
tools | ReactNode | Application-owned model selectors, capability controls, or other tools. | |
renderAttachments | (attachments: AttachmentData[], actions: MessageComposerAttachmentActions) => ReactNode | ||
renderLayout | (parts: MessageComposerParts, state: MessageComposerState) => ReactNode | Rearranges the provided controls. Render input and submit once; feedback stays in the form. | |
textareaProps | MessageComposerTextareaProps | {} | Native textarea attributes and handlers. Prevent a key's default to override its shortcut. |
maxLength | number | ||
sendOnEnter | boolean | false | Enter submits, Shift+Enter inserts a line. Otherwise use Cmd/Ctrl+Enter. |
generating | boolean | false | Application-owned response generation, separate from submission pending state. |
onStop | () => void | Requests that the application stop generation; does not itself cancel a network request. | |
compact | boolean | false | A single-line draft with an inline icon action; grows as the message wraps. |
maxRows | number | 6 | Maximum visible draft lines in compact mode, clamped to 1–20. |
Also accepts Omit<FormHTMLAttributes<HTMLFormElement>, "defaultValue" | "onSubmit">.
Reads the nearest composer's draft and validated commands from a custom child or tool.
| Keys | Does |
|---|---|
| Cmd+Enter, Ctrl+Enter | Submits the draft when no send or response generation is in progress. |
| Enter, Shift+Enter | With sendOnEnter enabled, Enter submits and Shift+Enter inserts a line; IME composition never submits. |
| Tab, Enter, Space | Operates attachment, send, and stop actions. |
<form class="rs-message-composer" aria-label="Message"><label>Message<textarea class="rs-textarea" placeholder="Write a message…"></textarea></label><div class="rs-message-composer-actions"><button class="rs-btn-primary" type="submit">Send</button></div></form>