Message
Chat message bubbles, attachments, and conversation markers.
Quick Preview
Composition
Message is the bubble shell. It owns direction, size, the tail corner, and the
meta row (timestamp + read receipt), and exposes size/direction to its parts
through data-* attributes — so the content and attachment slots size and colour
themselves with no props of their own.
MessageGroup
├── Marker (optional — date divider, system notice, unread line)
└── Message (direction, size, timestamp, status, footer)
├── MessageContent (prose)
├── MessageMedia (image / video)
│ ├── MessageMediaImage | MessageMediaPlaceholder
│ ├── MessageMediaPlay
│ └── MessageMediaDuration
├── MessageFile (document)
│ ├── MessageFileIcon
│ ├── MessageFileInfo › MessageFileName · MessageFileDetail
│ └── MessageFileAction
└── MessageVoice (voice note)
├── MessageVoicePlay
├── MessageVoiceWaveform
└── MessageVoiceDurationPick exactly one content slot per bubble — MessageContent, MessageMedia,
MessageFile, or MessageVoice. The shell renders the meta row itself.
Usage
<MessageGroup>
<Message direction="received" timestamp="14:30">
<MessageContent render={<p />}>Is it still available?</MessageContent>
</Message>
<Message direction="sent" status="read" timestamp="14:32" footer="Sent by AI">
<MessageContent render={<p />}>Yes — see you there.</MessageContent>
</Message>
</MessageGroup>Examples
Conversation thread
Set direction per side and pass a timestamp. Sent messages show the Figma
read-receipt tick by default; set showReadReceipt={false} to hide it. Use
status="delivered" to change its accessible label or status="failed" to
render legible failure text instead of an icon. The optional footer sits below
a sent bubble for quiet attribution.
Attachments
Swap MessageContent for MessageMedia, MessageFile, or MessageVoice. Each
reads the bubble's size and direction from the shell, so a picture, a
document, and a voice note line up on the same ladder without extra props.
Markers
Use Marker as a sibling of messages — not a child — for date dividers
(variant="pill"), system notices, and unread lines. Give it real semantics with
render (e.g. render={<h3 />}) since the same component serves several
meanings.
Today
Props
| Prop/API | Type | Default | Description |
|---|---|---|---|
direction on Message | "sent" | "received" | "sent" | Conversation side. Drives fill, alignment, and the tail corner. |
size on Message | "sm" | "md" | "lg" | "xl" | "md" | Size ladder for padding, radius, gaps, and type. |
timestamp on Message | Date | string | - | Value for the <time> element. Omit to drop the timestamp. |
timestampDateTime on Message | Date | string | - | Machine-readable value for a display-string timestamp. |
status on Message | "read" | "delivered" | "failed" | "read" | Delivery receipt state. Sent-only; ignored when received. |
showTimestamp / showReadReceipt on Message | boolean | true | Toggle the meta-row parts. |
footer on Message | ReactNode | - | Quiet attribution below a sent bubble. |
variant on Marker | "default" | "pill" | "default" | Full-width notice vs centred date pill. |
size on Marker | "sm" | "md" | "lg" | "xl" | "md" | Match the surrounding bubbles. |
alt on MessageMediaImage | string | - | Required — a chat photo is content, never decorative. |
render on MessageContent / MessageFileAction / Marker | ReactElement | - | Polymorphic element (e.g. <p />, <a download />, <h3 />). |
className | string | - | Local layout or spacing overrides. |
| native/root props | React component props | - | Passed through to the underlying element. |
Accessibility
- Preserve the documented composition so the bubble's
size/directioncontext reaches its content and attachment slots. MessageMediaImagerequiresalt; treat chat images as content, not decoration.- The read receipt does not rely on colour alone —
readanddeliveredexpose an accessible label, andfailedrenders text. - Give
Markerreal semantics withrenderwhere they exist (a heading for a date,role="separator"for an unread line). - Provide accessible names for the media, file, and voice controls; the play/download controls are real buttons with sensible defaults.