Message
A Message shows users one entry of a conversation, with the sender’s avatar, a header, the content, and a footer.
When to Use
- for one entry of a conversation, such as a client’s note on a draft or the reply to it
- to show the sender’s name above the text and the time under it
- to keep a file or an action with its text, such as an Attachment under the Bubble or “Copy the message” in the footer
- as a
MessageGroup, for consecutive Messages from one sender
When Not to Use
- for the frame around the text alone (use a Bubble)
- for a line that no sender wrote, like a date or “Camille sent INV-2026-014” (use a Marker)
- for a row of media, text, and actions outside a conversation (use the Item component)
Anatomy
A conversation is built from Message and the components that are placed inside it or around it.
One Message
| Element | Usage |
|---|---|
| Header | Optional |
| Avatar | Optional |
| Content | Required |
| Footer | Optional |
| Container | Required |
Message places the avatar and the content side by side. MessageContent stacks its content: the header, a Bubble, an Attachment, and the footer.
A Conversation
| Element | Usage |
|---|---|
| Message scroller | Optional |
| Marker | Optional |
| Message | Required |
| Avatar | Optional |
| Bubble | Optional |
| Attachment | Optional |
| Component | Placement |
|---|---|
| Message Scroller | Around the conversation, with each row in a MessageScrollerItem |
| Message | One row for each entry |
| Avatar | In MessageAvatar |
| Bubble | In MessageContent, around the text |
| Attachment | In MessageContent, under the Bubble |
| Marker | Between two Messages for a date or an event, or in MessageContent for a status |
Behavior
A Message fills the width of its container, and a word that is longer than the row breaks.
Alignment
To reverse the row, set align="end". The avatar moves to the end side, and the header, the content, and the footer are aligned to the end. The markup keeps its order.
Set align on the Message, not on the Bubble inside it.
Avatar
MessageAvatar is a column at least 32px wide, which is the size of a default Avatar, at the bottom of the row. It clips its content to a circle, so a square Avatar loses its corners.
Groups
MessageGroup stacks Messages. Show the avatar on the last Message, and keep an empty MessageAvatar on the ones before it. The empty slot keeps the column, so all the Bubbles start at the same edge.
MessageAvatar on the Messages above the avatar.Content
| String | Rule | Example | Counterexample |
|---|---|---|---|
| Header | Use the sender’s name | “Elise Martin” | “EM” |
| Footer | Use sentence case | “Sent at 9:41 AM” | “SENT 9:41 AM” |
aria-label of an action | Name the action and its object | “Copy the message” | “Copy” |
Accessibility
The parts of a Message are div elements with no role, and none is focusable. Screen readers read the parts in the order of the markup: the avatar, the header, the content, and the footer.
An icon-only Button in the footer has no text, so it needs an aria-label for screen readers to announce it, as
WCAG 2.2 SC 4.1.2 Name, Role, Value requires. Show the same words in a Tooltip, as for any icon-only
Button.
To report that a Message is on its way, such as “Sending the invoice”, place a Marker with role="status" in MessageContent. Screen readers then announce it without moving focus.
Installation
npx shadcn@latest add @summit/messageThe CLI adds no other component. For the parts that go inside a Message, add @summit/bubble, @summit/avatar, @summit/attachment, and @summit/marker.
Usage
import { Bubble, BubbleContent } from '@/components/ui/bubble';
import { Message, MessageContent } from '@/components/ui/message';
<Message>
<MessageContent>
<Bubble variant="secondary">
<BubbleContent>The homepage draft looks great. Can we start on Monday?</BubbleContent>
</Bubble>
</MessageContent>
</Message>;With a Sender and a Time
Place MessageAvatar before MessageContent, and place MessageHeader and MessageFooter around the Bubble.
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Message, MessageAvatar, MessageContent, MessageFooter, MessageHeader } from '@/components/ui/message';
<Message>
<MessageAvatar>
<Avatar>
<AvatarFallback>EM</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<MessageHeader>Elise Martin</MessageHeader>
<Bubble variant="secondary">
<BubbleContent>The homepage draft looks great. Can we start on Monday?</BubbleContent>
</Bubble>
<MessageFooter>Sent at 9:41 AM</MessageFooter>
</MessageContent>
</Message>;From the Current User
Set align="end", and use the default variant for the Bubble.
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>Yes. I will send a new draft tomorrow.</BubbleContent>
</Bubble>
</MessageContent>
</Message>A Group
Keep MessageAvatar empty on all the Messages except the last.
import { MessageGroup } from '@/components/ui/message';
<MessageGroup>
<Message>
<MessageAvatar />
<MessageContent>
<Bubble variant="secondary">
<BubbleContent>The homepage draft looks great.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarFallback>EM</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="secondary">
<BubbleContent>Can we move the booking button into the header?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>;With Actions
Place icon-xs ghost Buttons in MessageFooter, each with an aria-label and a Tooltip. The footer sets no gap, so add gap-1.
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { CopyIcon } from '@phosphor-icons/react';
<MessageFooter className="gap-1">
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={<Button variant="ghost" size="icon-xs" aria-label="Copy the message" />}>
<CopyIcon />
</TooltipTrigger>
<TooltipContent>Copy the message</TooltipContent>
</Tooltip>
</TooltipProvider>
</MessageFooter>;With an Attachment
Place an Attachment after the Bubble in MessageContent. It is aligned to the same side as the Message.
import { Attachment, AttachmentContent, AttachmentMedia, AttachmentTitle } from '@/components/ui/attachment';
import { FileTextIcon } from '@phosphor-icons/react';
<MessageContent>
<Bubble variant="secondary">
<BubbleContent>Here is the signed contract.</BubbleContent>
</Bubble>
<Attachment>
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>CTR-2026-007.pdf</AttachmentTitle>
</AttachmentContent>
</Attachment>
</MessageContent>;With a Status
While a Message is on its way, show a Marker with role="status" in place of the Bubble.
import { Marker, MarkerContent, MarkerIcon } from '@/components/ui/marker';
import { Spinner } from '@/components/ui/spinner';
<MessageContent>
<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Sending the invoice</MarkerContent>
</Marker>
</MessageContent>;API Reference
The parts accept the props of a div.
Message
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'start' | 'end' | 'start' | The side of the row that the Message is aligned to |