A conversation laid out in time: MessageItem renders one Message whose fields decide its shape, plus MessageStatus and three demo composers.
Messaging
A conversation laid out in time: MessageItem renders one Message whose fields decide its shape, plus MessageStatus and three demo composers.
Specimen
Guidelines
Code
Demo
Pick a shape and a side, and copy the result. The shape is which fields Message carries; the composer is subject two.
Phoenix Baker
Can you review the latest design when you have a minute?
🎉3
👍
Component*
Shape*
Side*
import type { Message } from "@/components/application/messaging/messaging";
import { MessageItem } from "@/components/application/messaging/messaging";
const message: Message = {
id: "message-001",
user: {
name: "Phoenix Baker",
avatarUrl: "https://www.untitledui.com/images/avatars/phoenix-baker?fm=webp&q=80",
status: "online",
},
sentAt: "Thursday 11:40am",
text: "Can you review the latest design when you have a minute?",
reactions: [{ content: "🎉", count: 3 }, { content: "👍", count: 1 }],
};
<ol aria-label="Conversation" className="flex flex-col gap-4">
<MessageItem msg={message} />
</ol>
One component, one type, three composers
MessageItem renders one Message as a list item, and the Message’s optional fields decide what it draws: text, reply, image, audio, video, attachment, urlPreview, reactions, and typing. user.me puts it on the sent side. The composers are separate exports from a second file, and the copied snippet for one is a single element.
Contextual demo
A message list is most of a screen: three threads in real chrome, one per composer.
The kit’s own message-chat-menu.tsx renders a thread of six messages in an ol named Conversation with MessageActionMinimal at size="md" in the footer, and ai-assistant-message-menu.tsx renders an assistant thread with showUserLabel={false}, an audio reply, a typing bubble, and MessageActionAdvanced underneath. The content dividers page’s preview renders MessageItem too, one list per day with a divider between the lists; the page itself builds its own thread.
The family
Two components and a type from messaging.tsx, and three composers from message-action.demo.tsx, a kit demo file.
The components in this family, with their descriptions and import paths
One message as a list item: the avatar, the name and time header, and whichever body the Message carries, with the actions panel hanging off the bubble.
The 16px sent, read, or failed glyph in the header, a button named from its tooltip. Rendered by MessageItem when the Message has a status; exported on its own too.
A framed, resizable textarea with a microphone, a sender picker, and Shortcuts and Attach in a footer row. The AI assistant menu’s composer. A kit demo composition.
The received message’s 32px avatar at size sm, with the online or offline dot from user.status and the name as its alt; a sent message draws none. The Advanced composer’s sender picker uses one at xs, forced to 16px with size-4.
The composers’ named actions: a secondary icon-only Send with Send01 in Minimal, named Send by aria-label since 2026-08-23, a link-color Send in Textarea, type=submit since the same day, and link-gray Shortcuts and Attach with 16px icons in Advanced.
The composers’ icon-only controls at xs tertiary: Record, Attach a file, and Add an emoji in Textarea, Record a voice message in Advanced, each named from its tooltip since 2026-08-23.
The video player’s filled play glyph at 20 and 16px, from base/video-player’s own icon file — the same triangle the audio bubble draws inline. Since 2026-08-30.
Stars02, Edit04, DownloadCloud02, RefreshCcw02, and Copy01 on the actions panel; Link03 on the link card; Send01, Recording02, Attachment01, FaceSmile, Microphone02, ItalicSquare, and ChevronDown in the composers.
The message shapes
The sheet’s nine types plus its reaction chips, each received and sent, all live: hover or tab into a bubble and the actions panel appears.
Text
Phoenix Baker
Can you review the latest design when you have a minute?
received text: the bubble, squared on the start corner when received and the end corner when sent
You
Sure, I’ll have a look this afternoon.
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Reply
Phoenix Baker
Sure, I’ll have a look this afternoon.
Done, I left two comments on the header.
received reply with text: the quoted nest above the text, one line with an ellipsis, bg-primary in a received bubble and bg-secondary in a sent one
You
Can you review the latest design when you have a minute?
Thanks, fixing them now.
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Image
Phoenix Baker
Dashboard wireframe.png1.4 MB
received image on its own: a 360px width, a 0.5px hairline, and the name and size as a caption
You
Dashboard wireframe.png1.4 MB
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Audio
Phoenix Baker
00:28
received audio: play, the waveform clipped to its row, and the duration, in a 480px box
You
00:28
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Video
Phoenix Baker
received video: the 16:9 player at the image’s 360px width — the poster, a blurred play disc, and the gradient bar, all drawings
You
sent, user.me: the same shape on the end side with no avatar and “You” as the name
File
Phoenix Baker
Tech requirements.pdf
1.2 MB
received attachment: a 40px file icon with the name and size, a 360px box with 12px of padding, filled on both sides
You
Tech requirements.pdf
1.2 MB
sent, user.me: the same shape on the end side with no avatar and “You” as the name
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Reactions
Phoenix Baker
Shipped the new header this morning.
🎉3
👍
received text with reactions: the chips end-aligned under the bubble, a count only when it is more than 1
You
Shipped the new header this morning.
🎉3
👍
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Writing
Phoenix Baker
Phoenix Baker is typing
received typing: the 40×28 bubble under the name, a role=status that says who is typing
The corner follows the side
Read from the source: every bubble squares one top corner, the start one on a received message and the end one on a sent message, with rounded-ss-none and rounded-se-none. Until 2026-08-23 the audio and file bubbles squared the top-left on both sides, so a sent audio message pointed at the wrong edge, and every corner was physical rather than logical. The 360px cap on images and files and the 480px cap on audio are the sheet’s and are new the same day; a text bubble has no cap and fills its column.
Video and Link preview draw since 2026-08-30
Both had been Figma-only. The video field was typed on the Message type with no branch that rendered it; it draws the sheet’s 16:9 player now, with the file’s own first frame as the still. urlPreview drew only the sheet’s Link minimal, a title and a description beside a link icon; its optional image now draws the Link preview’s OG cover, and the sheet’s URL line is the message’s own text. The player’s controls are named drawings with no handlers, like the audio play button, and there is still no URL field — both recorded on the Code tab.
The sheet’s image is 4:3
Its one Image symbol draws a 4:3 photo. The kit renders the file at its own aspect with object-cover and a full width, which is a rule rather than a specimen, so the cell above is whatever shape the sample file is. Recorded, not changed.
What the kit draws that the sheet does not
Two compositions the Message type allows and one prop, none drawn by a symbol. Guidelines says when to avoid them.
Phoenix Baker
Here’s the latest wireframe.
text with image: the picture inside the text bubble, between the reply and the text; the sheet draws two messages
You
You is typing
typing on the sent side: the same bubble under “You”, squared on the start corner either way
Can you review the latest design when you have a minute?
showUserLabel={false}: no name in the header, which the AI assistant menu passes on every message
States
Three status glyphs, the typing bubble, and the actions panel on hover or focus — which the kit draws on every bubble.
status, on a sent message
You
Sure, I’ll have a look this afternoon.
status="sent": one check in fg-quaternary, named Unread
You
Sure, I’ll have a look this afternoon.
status="read" with readAt: two checks in fg-brand-secondary, named Read Thursday 11:42am
You
Sure, I’ll have a look this afternoon.
status="failed": a filled alert circle in fg-error-primary, named Failed
the actions panel
Olivia Rhye
Can you review the latest design when you have a minute?
hover the bubble, or tab to Generate with AI: four buttons in a dark panel 20px below the end corner
Olivia Rhye
Tech requirements.pdf
1.2 MB
on a file bubble the second button is Download rather than Edit message
The status glyph is a named button
Read from the source: MessageStatus wraps a React Aria button in the kit’s Tooltip, and since 2026-08-23 the tooltip’s title is also the button’s aria-label, so Unread, Read with the readAt, and Failed are what a reader hears rather than an empty button. It draws the kit’s 2px focus ring at a 2px offset, and the glyph is hidden. The sheet attaches the status to Sent messages only; the kit renders it wherever status is set.
The panel shows on focus since 2026-08-23
It used to be opacity-0 until hover alone, so its four buttons were tab stops a keyboard user could land on without seeing. It carries focus-within:opacity-100 now, and motion-reduce:transition-none. The buttons themselves are drawings: none takes a handler, and MessageItem forwards nothing to them. The panel is painted with the dark palette in both modes through a dark-mode class, so its bg-primary_alt is the dark value even on a light page.
Composers
The sheet’s three Message action symbols: Minimal at three sizes, Textarea, and Advanced. Minimal and Textarea submit.
Minimal
size="sm": the input and the Send button at 36px, in a 360px row
size="md": the input and the Send button at 40px, in a 360px row
size="lg": the input and the Send button at 44px, in a 360px row
Textarea: a 128px field with Record in the top corner; Attach, Emoji, and a link Send in the bottom one
Advanced: a framed, resizable field with a microphone; the sender picker and Shortcuts and Attach in the footer
Demo compositions, not components
The file is message-action.demo.tsx: three forms the kit ships as examples of what a composer looks like, each wrapping the base InputBase or TextAreaBase with the kit’s buttons. Minimal takes a size; the other two take a class for the field and nothing else. The sheet’s Minimal row is items-start since 2026-08-23, as the symbol draws it. Minimal’s icon-only Send is named Send by aria-label since the same day, and Textarea’s Send is type="submit" since the same day; the composer exposes no prop to rename the first, and Advanced still has no Send, which the Code tab records.