Components
Chat
A composable chat interface with a scrollable, auto-scrolling message thread and a configurable message input. Composes the Avatar and Button components. Bring your own message data; override any part as needed.
Installation
bunx @happlyui/cli@latest add chat
Dependencies
npm packages
@remixicon/react
Registry dependencies
These are automatically installed when you add this component.
tvhapply-ui-utilsavatarbutton
Usage
import * as Chat from "@/components/ui/chat"
<Chat.Root>
<Chat.List>
<Chat.Message side="received" avatar={<Chat.Avatar src="/avatar.png" />}>
<Chat.Bubble>
Hello there!
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
</Chat.List>
<Chat.Input onSend={(text) => send(text)} />
</Chat.Root>
Examples
Default
A full thread with received and sent bubbles, short pills, and date / new-message dividers. Set a height on a parent so the list can scroll.
<div className="h-[600px] w-full max-w-2xl">
<Chat.Root>
<Chat.List>
<Chat.Message side="received" avatar={avatar}>
<Chat.Bubble>
Join us for an engaging seminar…
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
<Chat.Message side="sent">
<Chat.Bubble>
Explore the fundamentals…
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
<Chat.Divider>23 May, 2025</Chat.Divider>
<Chat.Message side="received">
<Chat.Bubble shape="pill">
Hey!
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
<Chat.Divider variant="feature">New message</Chat.Divider>
</Chat.List>
<Chat.Input onSend={(text) => send(text)} />
</Chat.Root>
</div>
Bubbles
Sent, received, and highlighted bubble tones. The tone defaults from the parent Message's side and can be overridden.
<Chat.Message side="received" avatar={avatar}>
<Chat.Bubble>
A received message.
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
<Chat.Message side="sent" avatar={avatar}>
<Chat.Bubble>
A sent message.
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
<Chat.Message side="received">
<Chat.Bubble tone="highlight">
A highlighted message.
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
Pills and Dividers
Short pill-shaped messages and both divider variants (date and highlighted new-message).
<Chat.Message side="received">
<Chat.Bubble shape="pill">
Hey!
<Chat.Timestamp>3:00 PM</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
<Chat.Divider>23 May, 2025</Chat.Divider>
<Chat.Divider variant="feature">New message</Chat.Divider>
Input
The default input: attachment + emoji buttons, an auto-growing field, and a send button. Uncontrolled by default; Enter sends, Shift+Enter inserts a newline.
<Chat.Input onSend={(text) => console.log(text)} />
Interactive
A live thread that appends messages on send. The list sticks to the bottom as new messages arrive.
const [messages, setMessages] = React.useState([])
<Chat.Root>
<Chat.List>
{messages.map((m) => (
<Chat.Message key={m.id} side={m.side} avatar={m.avatar}>
<Chat.Bubble>
{m.text}
<Chat.Timestamp>Now</Chat.Timestamp>
</Chat.Bubble>
</Chat.Message>
))}
</Chat.List>
<Chat.Input onSend={(text) => setMessages((p) => [...p, { id: p.length + 1, side: 'sent', text }])} />
</Chat.Root>
Custom Input
Override the default input clusters with the leading / actions slots. Chat.SendButton forwards Button props, so restyling is just variant/mode.
<Chat.Input
placeholder="Ask anything…"
showAttachment={false}
actions={<Chat.SendButton variant="primary" mode="filled" />}
onSend={(text) => send(text)}
/>
Disabled
Disabled input with muted styling.
<Chat.Input disabled placeholder="You cannot type here…" />
API Reference
Chat.Root
Vertical layout container. Fills its parent and pins the input 20px below the list. Set a height on it (or its parent) so the list can scroll.
| Prop | Type | Default | Description |
|---|
Chat.List
Scrollable message container. Auto-scrolls to the newest message when the user is near the bottom.
| Prop | Type | Default | Description |
|---|---|---|---|
autoScroll | boolean | true | Stick to the bottom as new messages arrive (unless the user has scrolled up) |
Chat.Message
A single message row. Reserves a 44px gutter on both sides so avatar-less rows stay aligned with rows that have an avatar.
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'sent' | 'received' | - | Which side the message is on. Drives alignment and the default bubble tone. |
avatar | React.ReactNode | - | Optional avatar rendered in the gutter on the message's side (e.g. <Chat.Avatar src="…" />) |
Chat.Avatar
Thin wrapper around the Avatar component, sized for chat rows (32px). Pass `src` for the common case, or compose Avatar parts as children. Forwards all Avatar.Root props (size, color, etc.).
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | - | Image source. Renders an Avatar.Image when provided. |
size | string | '32' | Avatar size token (forwarded to Avatar.Root) |
Chat.Bubble
The message bubble. Reads side from the parent Message to pick a default tone.
| Prop | Type | Default | Description |
|---|---|---|---|
shape | 'bubble' | 'pill' | 'bubble' | Rounded card ('bubble') or content-hugging pill ('pill') for short messages |
tone | 'received' | 'highlight' | 'sent' | - | Color tone. Defaults from the parent Message's side (sent → sent, otherwise received). |
Chat.Timestamp
Small caption inside a bubble. Adapts its color and position to the surrounding bubble.
| Prop | Type | Default | Description |
|---|
Chat.Divider
A centered label between two rules, e.g. a date or a new-message marker.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'feature' | 'default' | Neutral date label ('default') or highlighted primary label ('feature') |
Chat.Input
Batteries-included composer. Uncontrolled by default; pass value/onChange to control it. Enter sends, Shift+Enter inserts a newline.
| Prop | Type | Default | Description |
|---|---|---|---|
onSend | (value: string) => void | - | Fired when the user sends (Enter or the send button). Receives the trimmed text. |
value | string | - | Controlled value. Omit for uncontrolled behavior. |
onChange | (value: string) => void | - | Fired on every keystroke |
placeholder | string | 'Write a message...' | Placeholder text |
onAttach | () => void | - | Click handler for the default attachment button |
onEmoji | () => void | - | Click handler for the default emoji button |
showAttachment | boolean | true | Show the attachment button |
showEmoji | boolean | true | Show the emoji button |
leading | React.ReactNode | - | Replace the left (attachment) cluster entirely |
actions | React.ReactNode | - | Replace the right (emoji + send) cluster entirely |
sendDisabled | boolean | - | Disable sending even when there is text |
maxHeight | number | 128 | Max auto-grow height in px before the field scrolls |
containerClassName | string | - | Additional classes for the input container |
Chat.SendButton
The send button, built on the Button component (neutral / lighter by default). Renders an arrow-up icon unless given children, and forwards all Button.Root props (variant, mode, loading, etc.).
| Prop | Type | Default | Description |
|---|