- Accordion
- Alert
- Alert Dialog
- Attachment
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Checkbox
- Collapsible
- Command
- Context Menu
- Data Table
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Toggle Group
- Tooltip
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>
<span className="grid size-8 place-items-center">ME</span>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>Deploying to prod real quick.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<span className="grid size-8 place-items-center">R</span>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>It's 4:55 PM. On a Friday.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<span className="grid size-8 place-items-center">ME</span>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>It's a one-line change.</BubbleContent>
</Bubble>
<MessageFooter>Delivered</MessageFooter>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<span className="grid size-8 place-items-center">R</span>
</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>
)
}Message lays out one row of a conversation: the avatar, alignment, header and footer around the message surface. Render the surface itself with Bubble, and put the conversation in a MessageScroller.
Installation#
pnpm dlx shadcn@latest add https://ui.ikramhasan.com/r/message.json
Usage#
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>Composition#
Message
├── MessageAvatar
└── MessageContent
├── MessageHeader
├── Bubble
└── MessageFooterUse MessageGroup to stack consecutive messages from the same sender:
MessageGroup
├── Message
└── MessageExamples#
Avatar#
MessageAvatar sits at the bottom of the row, centered on a one-line bubble. Set align="end" on the message to move it to the other side.
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>
<span className="grid size-8 place-items-center">R</span>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The build failed during dependency installation.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<span className="grid size-8 place-items-center">R</span>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>Can you share the exact error?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<span className="grid size-8 place-items-center">R</span>
</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>
)
}Group#
Render an empty MessageAvatar on the earlier messages so they stay aligned with the avatar on the last one.
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>
<span className="grid size-8 place-items-center">CN</span>
</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#
MessageHeader holds a sender name and MessageFooter metadata such as a delivery status. Both line their text up with the bubble's.
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#
Put message actions in MessageFooter: ghost icon-sm buttons, whose first icon lines up with the bubble text.
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-sm"
aria-label="Copy"
title="Copy"
>
<CopyIcon />
</Button>
<Button
variant="ghost"
size="icon-sm"
aria-label="Like"
title="Like"
>
<ThumbsUpIcon />
</Button>
<Button
variant="ghost"
size="icon-sm"
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-sm"
title="Retry"
aria-label="Retry"
>
<RefreshCcwIcon />
</Button>
</MessageFooter>
</MessageContent>
</Message>
</div>
)
}Accessibility#
Message is a layout wrapper; accessibility comes from what you put in it.
- Icon-only actions in
MessageFooterneed anaria-label. - For in-progress messages, use a
Markerwithrole="status"so the update is announced.
API Reference#
| Part | Element | Props / notes |
|---|---|---|
Message | div | align: start · end (default start). |
MessageGroup | div | Stacks consecutive messages from the same sender, 8px apart. |
MessageAvatar | div | Bottom-aligned, centered on a one-line bubble. With a MessageFooter, it rises past the footer to stay level with the bubble. |
MessageContent | div | Wraps the header, the bubble and the footer, 8px apart. |
MessageHeader | div | Above the message, such as a sender name. Stays at the start. |
MessageFooter | div | A 28px row below the message for status or actions. Follows the message's side. |