Skip to content
Component installs need the registry setup
VegaStack Design

Message

Displays a message in a conversation, with optional avatar, header, footer, and alignment.

Status
stable
Since
0.1.0
Accessibility pattern
presentational layout

Last updated

ME
Deploying to prod real quick.
R
It's 4:55 PM. On a Friday.
ME
It's a one-line change.
Delivered
R
It's always a one-line change 😭.
Alright, let me take a look.
Oliver is typing...

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Message before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify message

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/message

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

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="/avatars/01.png" alt="@shadcn" />
      <AvatarFallback>CN</AvatarFallback>
    </Avatar>
  </MessageAvatar>
  <MessageContent>
    <Bubble>
      <BubbleContent>How can I help you today?</BubbleContent>
    </Bubble>
  </MessageContent>
</Message>;

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 whole conversation, use MessageScroller.

ME
Deploying to prod real quick.
R
It's 4:55 PM. On a Friday.
ME
It's a one-line change.
Delivered
R
It's always a one-line change 😭.
Alright, let me take a look.
Oliver is typing...

Anatomy

Message — data-slot="message"
MessageAvatar — data-slot="message-avatar"
MessageContent — data-slot="message-content"
MessageFooter — data-slot="message-footer"
MessageGroup — data-slot="message-group"
MessageHeader — data-slot="message-header"

Examples

Composition

MessageContent is the column beside the avatar: the header sits above the message surface and the footer below it.

Message
├── MessageAvatar
└── MessageContent
    ├── MessageHeader
    ├── Bubble
    └── MessageFooter

MessageGroup stacks consecutive messages from the same sender.

MessageGroup
├── Message
└── Message
O
Oliver
The avatar, the header, the surface and the footer are four slots around one row.
Delivered
Two rows, one sender.
ME
The group stacks them; the avatar sits on the last one.

Features

  • Start and end alignment for sender and receiver rows via the align prop
  • Avatar slot that anchors to the bottom of the message and stays clear of the footer
  • Header and footer slots for sender names, status, and message actions
  • Footer follows the message side; actions stay aligned on align="end" rows
  • Group wrapper for stacking consecutive messages from the same sender
  • Customizable styling through the className prop on every part
R
Rabbit
The row owns the layout; the bubble owns the surface.
ME
On an end-aligned row the avatar moves to the end and the footer follows it.
Read

Avatar

Use MessageAvatar to render an avatar next to the message. Set align="end" on the message to move the avatar to the end of the row.

alignDescription
startAlign the message to the start of the conversation.
endAlign the message to the end of the conversation.
R
The build failed during dependency installation.
R
Can you share the exact error?
R
Here's the error from the logs
Something went wrong with the build. The libraries are not installed correctly. Try running the build again.

Group

Use MessageGroup to stack consecutive messages from the same sender. Render an empty MessageAvatar on the earlier rows to keep them aligned with the avatar on the last one.

I checked the registry addresses.
CN
The component and example JSON now live under the UI registry.

Use MessageHeader for a sender name and MessageFooter for metadata such as a delivery or read status. The header does not re-justify its contents with align — its text reads from the start either way; the footer follows the message side.

Olivia
I already checked the logs.
Send the report to the team. Ping @shadcn if you need help.
Read Yesterday

Actions

Place message-level actions in MessageFooter — copy, retry or feedback buttons. Icon-only actions each carry an aria-label.

The install failure is coming from the workspace package.
Okay drop me a link. Taking a look...
Failed to send

Attachment

An Attachment is a sibling of the bubble inside MessageContent, so it inherits the row's alignment. A vertical image attachment sits above the bubble that talks about it; a file attachment sits below.

Workspace
Here's the image. Can you add it to the PDF? Use it for the cover page.
Done. Here's the PDF with the image added as the cover page.
sales-dashboard.pdfPDF · 2.4 MB
Thanks. Looks good.

API Reference

Message

The message row wrapper. A <div>; every other div prop passes through.

PropTypeDefaultDescription
align"start" | "end""start"The alignment of the message in the conversation.
classNamestring—Additional classes to apply to the row.

MessageGroup

Groups consecutive messages from the same sender. It adds no props of its own — a <div> with data-slot="message-group" and className.

MessageAvatar

The avatar slot, aligned to the bottom of the message. When the row has a MessageFooter, the avatar shifts up so it stays level with the message surface instead of the footer. It adds no props of its own — a <div> with data-slot="message-avatar" and className.

MessageContent

Wraps the header, the message surface and the footer. It adds no props of its own — a <div> with data-slot="message-content" and className.

MessageHeader

Displays content above the message, such as a sender name; its contents are not re-justified by align. It adds no props of its own — a <div> with data-slot="message-header" and className.

MessageFooter

Displays content below the message, such as status or actions; aligns to the message side. It adds no props of its own — a <div> with data-slot="message-footer" and className.

Accessibility

  • Message is a presentational layout wrapper — six plain <div>s. Every accessible semantic comes from what you put inside it.
  • Action buttons in MessageFooter are usually icon-only, so give each one an aria-label.
  • For an in-progress message, use a Marker with role="status" so assistive tech announces the update as it appears.
  • The component renders no control, so it contributes no focus affordance: whatever you render inside it brings the global 2px :focus-visible outline from base.css.
  • MessageContent is min-w-0 and wraps on word boundaries, so a long unbroken string reflows instead of widening the row past the viewport.
Every icon-only action below is named for a screen reader.
Checking the logs...
ContractStates tested
Behaviourdefault
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test
Visualdefault

Do / Don't

Do
Use Message for the conversation row — avatar, alignment, header and footer — and render the visible surface inside it with Bubble.
Don't
Style the message surface on Message itself, or use it as a generic two-column layout outside a conversation.

Deviations

Upstream's file plus packages/ui/upstream/patches/message.patch. Every hunk:

  • NO STYLING HUNK AT ALL — Message is six plain <div>s with a class each: no control, no focus affordance, no cursor, no disabled state, no live region and no portal, so not one row in the exception map reaches it. A11Y-2 is audited and not applicable — nothing here is a pointer target. message.test.tsx asserts the part list, the align switch and the avatar/footer offset, so a future upstream that adds a control to this file fails there.
  • DOC-2 — import { cn } from "cn" becomes @vegastack/design; our npm layer owns cn.
  • DOC-1 — prettier reflow plus the registry-stamp.mjs provenance header the three-copy model needs; no decision of its own.

On this page