const initialMessages = /* ... */;
<div className="flex h-80 w-full max-w-md flex-col gap-3">
<Conversation className="rounded-(--radius-panel) border border-border-muted">
{messages.map((message) => (
<Message key={message.id} role={message.role}>
<MessageContent>{message.text}</MessageContent>
</Message>
))}
</Conversation>
<PromptInput
onSubmit={(e) => {
e.preventDefault()
if (!draft.trim()) return
setMessages((current) => [
...current,
{ id: current.length + 1, role: "user" as const, text: draft },
])
setDraft("")
}}
>
<PromptInputTextarea
value={draft}
onChange={(e) => setDraft(e.target.value)}
placeholder="Ask anything…"
aria-label="Message"
/>
<PromptInputToolbar>
<PromptInputSubmit isIconOnly aria-label="Send message">
<ArrowUpIcon />
</PromptInputSubmit>
</PromptInputToolbar>
</PromptInput>
</div>Installation
npx shadcn@latest add @dotui/chatUsage
import { Conversation, Message, MessageContent } from "@/ui/chat"<Conversation>
<Message role="user">
<MessageContent>How do I center a div?</MessageContent>
</Message>
<Message role="assistant">
<MessageContent>Center it with place-items.</MessageContent>
</Message>
</Conversation>Anatomy
Conversation is the scroll container for the message list. Each Message sets
the role its children style against: user renders MessageContent as a
contained bubble aligned to the end of the row, assistant renders it plain and
full-width. MessageAvatar is optional and sits on the sender's side of the row.
import { Conversation, Message, MessageAvatar, MessageContent } from "@/ui/chat"<Conversation>
<Message role="assistant">
<MessageAvatar name="AI" />
<MessageContent>{/* message body */}</MessageContent>
</Message>
</Conversation>PromptInput is the composer. It renders a form, so PromptInputSubmit — or
pressing Enter in the textarea — submits it; Shift+Enter inserts a newline. The
textarea grows with its content, and the shell is the same input group the rest
of your fields use, so it follows their style, radius and density.
import {
PromptInput,
PromptInputSubmit,
PromptInputTextarea,
PromptInputToolbar,
} from "@/ui/chat"<PromptInput onSubmit={handleSubmit}>
<PromptInputTextarea placeholder="Ask anything…" aria-label="Message" />
<PromptInputToolbar>
{/* attachments, tools… */}
<PromptInputSubmit isIconOnly aria-label="Send message">
<ArrowUpIcon />
</PromptInputSubmit>
</PromptInputToolbar>
</PromptInput>Examples
Basic
With Avatars
Prompt Input
With Toolbar
API Reference
Conversation
Message
A single turn in a conversation. Lays out the avatar and the content, and exposes the role its children style against.
Supports all div attributes.
| Prop | Type | Default | |
|---|---|---|---|
MessageContent
The body of a message — text, or any rich content the message carries.
Supports all div attributes.
| Prop | Type | Default | |
|---|---|---|---|
MessageAvatar
The avatar of the message sender.
Supports all span attributes.
| Prop | Type | Default | |
|---|---|---|---|
"lg" | "md" | "sm" | 'sm' | ||
string | — | ||
PromptInput
The composer users type their message into. Renders a form, so a submit button — or pressing Enter in the textarea — submits it.
Supports all form attributes.
| Prop | Type | Default | |
|---|---|---|---|
PromptInputTextarea
The auto-growing textarea of the composer. Enter submits the surrounding form, Shift+Enter inserts a newline.
Supports all textarea attributes.
| Prop | Type | Default | |
|---|---|---|---|
PromptInputToolbar
The row of actions below the textarea, such as attachments or the submit button.
Supports all div attributes.
| Prop | Type | Default | |
|---|---|---|---|
PromptInputSubmit
The button that submits the composer.
Supports all button attributes.
| Prop | Type | Default | |
|---|---|---|---|
union | 'primary' | ||
union | 'md' | ||
Last updated on 8/28/2026