Messaging

A conversation laid out in time: MessageItem renders one Message whose fields decide its shape, plus MessageStatus and three demo composers.

Demo

Pick a shape and a side, and copy the result. The shape is which fields Message carries; the composer is subject two.

  1. Phoenix Baker
    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.

Two slideout menus are consumers

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
ComponentDescriptionImport from
MessageItemOne 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.@/​components/​application/​messaging/​messaging
MessageStatusThe 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.@/​components/​application/​messaging/​messaging
MessageThe type. Which optional fields it carries decides the shape: text, reply, image, audio, video, attachment, urlPreview, reactions, typing, and status.@/​components/​application/​messaging/​messaging
MessageActionMinimalA one-line composer: an InputBase and an icon-only Send button in a 360px row, at sm, md, or lg. A kit demo composition.@/​components/​application/​messaging/​message-action.demo
MessageActionTextareaA 128px textarea with Record in its top corner and Attach, Emoji, and a link Send in the bottom one. A kit demo composition.@/​components/​application/​messaging/​message-action.demo
MessageActionAdvancedA 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.@/​components/​application/​messaging/​message-action.demo

Built from

Ten pieces make up the family; nine have their own docs page.

Component
Where it’s used here
AvatarThe 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.
TooltipWraps the status button. Its title, Unread, Read with the readAt, or Failed, is also the button’s aria-label since 2026-08-23.
ButtonThe 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.
ButtonUtilityThe 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.
InputBaseThe Minimal composer’s field, at the size prop, named Message and submitted as the message field.
TextAreaBaseThe Textarea and Advanced composers’ 128px field: resize-none in one, resize-y with a 12px radius in the other.
FileIconThe file bubble’s 40px glyph from @untitledui/file-icons, typed from attachment.type, drawn twice so a light and a dark theme swap with the mode.
PlayIconThe 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.
@untitledui/iconsStars02, 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
  1. Phoenix Baker
    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
  1. 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
  1. Phoenix Baker
    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
  1. 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
  1. Phoenix Baker
    Phoenix Baker
    Wireframe of the dashboard layout
    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
  1. You
    Wireframe of the dashboard layout
    Dashboard wireframe.png1.4 MB
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Audio
  1. Phoenix Baker
    Phoenix Baker

    00:28

received audio: play, the waveform clipped to its row, and the duration, in a 480px box
  1. You

    00:28

sent, user.me: the same shape on the end side with no avatar and “You” as the name
Video
  1. Phoenix Baker
    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
  1. You
sent, user.me: the same shape on the end side with no avatar and “You” as the name
File
  1. Phoenix Baker
    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
  1. 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
Link minimal
  1. Phoenix Baker
    Phoenix Baker
    https://www.untitledui.com
received text with urlPreview: the link card in the bubble, the URL line under it
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Link preview
  1. Phoenix Baker
received text with urlPreview.image: the OG cover in the bubble, a 1200/630 crop, with the URL line under it
sent, user.me: the same shape on the end side with no avatar and “You” as the name
Reactions
  1. Phoenix Baker
    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
  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
  1. Phoenix Baker
    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.

  1. Phoenix Baker
    Phoenix Baker
    Wireframe of the dashboard layout
    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
  1. You
    You is typing
typing on the sent side: the same bubble under “You”, squared on the start corner either way
  1. Lana Steiner
    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
  1. You
    Sure, I’ll have a look this afternoon.
status="sent": one check in fg-quaternary, named Unread
  1. You
    Sure, I’ll have a look this afternoon.
status="read" with readAt: two checks in fg-brand-secondary, named Read Thursday 11:42am
  1. You
    Sure, I’ll have a look this afternoon.
status="failed": a filled alert circle in fg-error-primary, named Failed
the actions panel
  1. Olivia Rhye
    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
  1. Olivia Rhye
    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.