Message
Lay out a chat message with an avatar, sender name, timestamp, footer actions and start or end alignment.
Preview
Usage
import {
Message,
MessageAvatar,
MessageContent,
MessageFooter,
MessageGroup,
MessageHeader,
} from '@vitnode/core/components/ui/message'<Message>
<MessageAvatar>
<Avatar>
<AvatarImage alt="VitNode assistant" src="/logo_vitnode_icon.svg" />
<AvatarFallback>VN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>How can I help you today?</BubbleContent>
</Bubble>
</MessageContent>
</Message>Message owns the row: who's talking, which side, and what sits above or
below. The speech bubble is a Bubble.
MessageGroup
└── Message
├── MessageAvatar
└── MessageContent
├── MessageHeader
├── Bubble / Attachment
└── MessageFooterAlignment
align="end" marks messages the current user sent. The avatar swaps sides,
and every bubble, attachment, header and footer inside follows, so you don't
set align on each Bubble. Sides flip on their own in right-to-left
languages.
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>Sent by me, obviously.</BubbleContent>
</Bubble>
</MessageContent>
</Message>Avatar
MessageAvatar sits at the bottom, next to the last bubble. With a
MessageFooter, it lifts to line up with the bubble instead.
Group
MessageGroup tightens the spacing between messages from one sender. Give the
earlier ones an empty MessageAvatar so their bubbles line up.
<MessageGroup>
<Message>
<MessageAvatar />
<MessageContent>...</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarFallback>AV</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>...</MessageContent>
</Message>
</MessageGroup>For several bubbles in one message, use
BubbleGroup inside MessageContent.
Header, footer and actions
MessageHeader holds the sender name and time. MessageFooter holds read
receipts or actions like copy and retry.
<MessageContent>
<MessageHeader className="gap-1.5">
<span className="text-foreground">Ava</span>
<time dateTime="2026-10-02T09:41">9:41 AM</time>
</MessageHeader>
<Bubble variant="muted">
<BubbleContent>Did the plugin build pass?</BubbleContent>
</Bubble>
<MessageFooter>
<Button aria-label="Copy message" size="icon-xs" variant="ghost">
<CopyIcon />
</Button>
</MessageFooter>
</MessageContent>Attachments
Drop an Attachment next to the bubble in
MessageContent. It follows the message's alignment.
Accessibility
Messagehas no role. Putrole="log"and anaria-labelon the conversation container.- Alignment only helps sighted users. Name the sender in text too, with a
MessageHeader(className="sr-only"for your own messages). - Wrap timestamps in
<time dateTime="...">. - Give
AvatarImagethe sender's name asalt. If a header already names them, setaria-hidden="true"onMessageAvatar. - Icon-only footer actions need an
aria-label. - For "someone is typing", use a Marker with
role="status".
Props
Message
Prop
Type
The other parts accept every div prop, including className.