Composer
The prompt input, and the clearest expression of the seam language: the whole surface is a debossed well you act into; the send key inside it is the embossed token that fires the action.
Enter to send · ⇧ Enter for a new line
tsx
"use client"
import * as React from "react"
import { Paperclip } from "lucide-react"
import { Button } from "@/registry/seam/ui/button"
import {
Composer,
ComposerSubmit,
ComposerTextarea,
ComposerToolbar,
ComposerTools,
} from "@/registry/seam/ui/composer"
import { Kbd } from "@/registry/seam/ui/kbd"
export default function ComposerDemo() {
const [value, setValue] = React.useState("")
const submit = (e: React.FormEvent) => {
e.preventDefault()
if (!value.trim()) return
setValue("")
}
return (
<div className="w-full max-w-md">
<Composer onSubmit={submit}>
<ComposerTextarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Ask anything…"
/>
<ComposerToolbar>
<ComposerTools>
<Button
type="button"
variant="ghost"
size="icon"
className="size-8"
aria-label="Attach file"
>
<Paperclip />
</Button>
</ComposerTools>
<ComposerSubmit disabled={!value.trim()} />
</ComposerToolbar>
</Composer>
<p className="text-muted-foreground mt-2 text-center text-xs">
<Kbd>Enter</Kbd> to send · <Kbd>⇧</Kbd> <Kbd>Enter</Kbd> for a new line
</p>
</div>
)
}bunx --bun @seamui/cli@latest add composerAPI
| Prop | Type | Default | Description |
|---|---|---|---|
| status | "ready" | "streaming" | "ready" | While streaming, the submit key becomes a stop control (its type switches to button). |
| onStop | () => void | — | Fired by the stop key while status is streaming. |
| onSubmit | FormEventHandler | — | Composer renders a real <form>; Enter in the textarea submits it. |
| value / onChange | Textarea props | — | On ComposerTextarea — the controlled text; you own the string. |
| onRemove | () => void | — | On ComposerAttachment — renders a ghost remove key inside the chip. |
ComposerTextarea accepts all Textarea props; ComposerSubmit accepts all Button props.
Notes
- Fully controlled and transport-agnostic: you own
value,onSubmit, andstatus— the shape maps 1:1 onto the AI SDK'suseChat, with no runtime dependency on it. - Enter submits, Shift+Enter inserts a newline, and submitting via keyboard never steals focus from the textarea. The submit/stop control swaps its
aria-labeland buttontypewith the status. - The send/stop icon crossfades on opacity — identical under reduced motion. Attachment chips rise in and fall out on
springs.snappy, collapsing to opacity fades under reduced motion. - The well itself stays still — text entry is calm, with only a focus ring for feedback.
Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.