Summit
ComponentsConversation

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

EM
Elise Martin
Can we start Monday?
Sent at 9:41 AM
ElementUsage
HeaderOptional
AvatarOptional
ContentRequired
FooterOptional
ContainerRequired

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

Today
EM
Here is the signed contract.
CTR-2026-007.pdfPDF, 2.4 MB
Thanks. We start on Monday.
ElementUsage
Message scrollerOptional
MarkerOptional
MessageRequired
AvatarOptional
BubbleOptional
AttachmentOptional
ComponentPlacement
Message ScrollerAround the conversation, with each row in a MessageScrollerItem
MessageOne row for each entry
AvatarIn MessageAvatar
BubbleIn MessageContent, around the text
AttachmentIn MessageContent, under the Bubble
MarkerBetween 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.

DoKeep an empty MessageAvatar on the Messages above the avatar.
Don’tWhen the empty slot is missing, the first Bubble starts before the second, and users no longer read the two as one sender’s.

Content

StringRuleExampleCounterexample
HeaderUse the sender’s name“Elise Martin”“EM”
FooterUse sentence case“Sent at 9:41 AM”“SENT 9:41 AM”
aria-label of an actionName 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/message

The 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.

With a Sender and a Time
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.

From the Current User
<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.

A Group
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.

With Actions
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.

With an Attachment
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.

With a Status
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

PropTypeDefaultDescription
align'start' | 'end''start'The side of the row that the Message is aligned to

On this page