import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
Bubble,
BubbleContent,
BubbleGroup,
BubbleReactions,
} from "@/components/ui/bubble"
import { Marker, MarkerContent } from "@/components/ui/marker"
import {
Message,
MessageAvatar,
MessageContent,
MessageFooter,
} from "@/components/ui/message"
export function MessageDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-6 py-12">
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage src="https://ui.shadcn.com/avatars/10.png" alt="@me" />
<AvatarFallback>ME</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>Deploying to prod real quick.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://ui.shadcn.com/avatars/02.png"
alt="@rabbit"
/>
<AvatarFallback>R</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>It's 4:55 PM. On a Friday.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage src="https://ui.shadcn.com/avatars/10.png" alt="@me" />
<AvatarFallback>ME</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>It's a one-line change.</BubbleContent>
</Bubble>
<MessageFooter>Delivered</MessageFooter>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://ui.shadcn.com/avatars/02.png"
alt="@rabbit"
/>
<AvatarFallback>R</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<BubbleGroup>
<Bubble variant="muted">
<BubbleContent>
It's always a one-line change 😭.
</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>Alright, let me take a look.</BubbleContent>
<BubbleReactions aria-label="Reactions: thumbs up">
<span>👍</span>
</BubbleReactions>
</Bubble>
</BubbleGroup>
</MessageContent>
</Message>
<Marker role="status">
<MarkerContent className="shimmer">
<span className="font-medium">Oliver</span> is typing...
</MarkerContent>
</Marker>
</div>
)
}Installation
npx shadcn@latest add @fulldev/message
Usage
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageAvatar, MessageContent } from "@/components/ui/message"
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>How can I help you today?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
Note: Message owns the row layout, avatar, alignment, header, and footer.
Render the visible message surface inside it with
Bubble. For the scroll container around a
conversation, use MessageScroller.
Composition
Use the following composition to build a message:
Message
├── MessageAvatar
└── MessageContent
├── MessageHeader
├── Bubble
└── MessageFooter
MessageGroup
├── Message
└── Message
Avatar
Use MessageAvatar to render an avatar next to the message.
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent, BubbleGroup } from "@/components/ui/bubble"
import { Message, MessageAvatar, MessageContent } from "@/components/ui/message"
export function MessageAvatarDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-6 py-12">
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://ui.shadcn.com/avatars/03.png"
alt="@avatar"
/>
<AvatarFallback>R</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The build failed during dependency installation.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://ui.shadcn.com/avatars/10.png"
alt="@avatar"
/>
<AvatarFallback>R</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>Can you share the exact error?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://ui.shadcn.com/avatars/03.png"
alt="@avatar"
/>
<AvatarFallback>R</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<BubbleGroup>
<Bubble variant="muted">
<BubbleContent>Here's the error from the logs</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>
Something went wrong with the build. The libraries are not
installed correctly. Try running the build again.
</BubbleContent>
</Bubble>
</BubbleGroup>
</MessageContent>
</Message>
</div>
)
}| align | Description |
|---|---|
start | Align the message to the start of the conversation. |
end | Align the message to the end of the conversation. |
Group
Use MessageGroup to stack consecutive messages from the same sender.
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
Message,
MessageAvatar,
MessageContent,
MessageGroup,
} from "@/components/ui/message"
export function MessageGroupDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-6 py-12">
<MessageGroup>
<Message>
<MessageAvatar />
<MessageContent>
<Bubble variant="muted">
<BubbleContent>I checked the registry addresses.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://ui.shadcn.com/avatars/02.png"
alt="@avatar"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The component and example JSON now live under the UI registry.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>
</div>
)
}Header and Footer
Use MessageHeader for a sender name and MessageFooter for metadata such as a delivery or read status.
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
Message,
MessageContent,
MessageFooter,
MessageHeader,
} from "@/components/ui/message"
export function MessageHeaderFooterDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
<Message>
<MessageContent>
<MessageHeader>Olivia</MessageHeader>
<Bubble variant="muted">
<BubbleContent>I already checked the logs.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>
Send the report to the team. Ping @shadcn if you need help.
</BubbleContent>
</Bubble>
<MessageFooter>
<div>
Read <span className="font-normal">Yesterday</span>
</div>
</MessageFooter>
</MessageContent>
</Message>
</div>
)
}Actions
Place message-level actions in MessageFooter, such as copy, retry, or feedback buttons.
import {
CopyIcon,
RefreshCcwIcon,
ThumbsDownIcon,
ThumbsUpIcon,
} from "lucide-react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
import { Message, MessageContent, MessageFooter } from "@/components/ui/message"
export function MessageActionsDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The install failure is coming from the workspace package.
</BubbleContent>
</Bubble>
<MessageFooter>
<Button variant="ghost" size="icon" aria-label="Copy" title="Copy">
<CopyIcon />
</Button>
<Button variant="ghost" size="icon" aria-label="Like" title="Like">
<ThumbsUpIcon />
</Button>
<Button
variant="ghost"
size="icon"
aria-label="Dislike"
title="Dislike"
>
<ThumbsDownIcon />
</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>Okay drop me a link. Taking a look...</BubbleContent>
</Bubble>
<MessageFooter className="gap-2">
<span className="font-normal text-destructive">Failed to send</span>
<Button
variant="ghost"
size="icon-xs"
title="Retry"
aria-label="Retry"
>
<RefreshCcwIcon />
</Button>
</MessageFooter>
</MessageContent>
</Message>
</div>
)
}Attachment
"use client"
import { DownloadIcon, FileTextIcon } from "lucide-react"
import {
Attachment,
AttachmentAction,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentMedia,
AttachmentTitle,
} from "@/components/ui/attachment"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageContent } from "@/components/ui/message"
export function MessageAttachmentDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
<Message align="end">
<MessageContent>
<Attachment orientation="vertical">
<AttachmentMedia variant="image">
<img
src="https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80"
alt="Workspace"
/>
</AttachmentMedia>
</Attachment>
<Bubble>
<BubbleContent>
Here's the image. Can you add it to the PDF? Use it for the
cover page.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
Done. Here's the PDF with the image added as the cover page.
</BubbleContent>
</Bubble>
<Attachment>
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction
type="button"
title="Download"
aria-label="Download"
size="icon-sm"
variant="secondary"
>
<DownloadIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>Thanks. Looks good.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</div>
)
}Accessibility
Message is a presentational layout wrapper.
Label icon-only actions
Action buttons in MessageFooter are usually icon-only, so give each one an aria-label.
<MessageFooter>
<Button variant="ghost" size="icon" aria-label="Copy">
<CopyIcon />
</Button>
</MessageFooter>
Status updates
For in-progress messages, use a Marker with role="status" so assistive tech announces the update as it appears.
<Message>
<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Checking the logs...</MarkerContent>
</Marker>
</Message>
API Reference
Source: react/src/components/ui/message.tsx
Message
The message row wrapper.
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "end" | "start" | The alignment of the message in the conversation. |
className | string | - | Additional classes to apply to the row. |
MessageGroup
Groups consecutive messages from the same sender.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes to apply to the group root. |
MessageAvatar
The avatar slot, aligned to the bottom of the message.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes to apply to the avatar slot. |
MessageContent
Wraps the header, message surface, and footer.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes to apply to the content slot. |
MessageHeader
Displays content above the message, such as a sender name.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes to apply to the header. |
MessageFooter
Displays content below the message, such as status or actions.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes to apply to the footer. |