Conversation surfaces

These encode the Raft product shape. Reach for them before assembling anything chat-like out of primitives.

Contents

  • Which surface
  • ConversationPanel vs ThreadPanel
  • Activity inbox and workspace search
  • Composer vs Textarea
  • Sidebar structure
  • Message surfaces
  • Composer structure

Which surface

ScenarioComponent
Channel and DM listSidebarRoot
The main conversation columnConversationPanelRoot
A thread beside the conversationThreadPanelRoot
The scrolling list of messagesMessageList + MessageItem
One human or agent messageMessageItem
Join/leave/rename event in the flowSystemMessage
Message inputComposerRoot + Composer
Notification inboxNotificationCenter
Activity or saved-item inboxActivityInboxPanel
Workspace search resultsSearchShellRoot
Live agent activity stripLiveAgentActivityBar

ConversationPanel vs ThreadPanel

They are not the same component with a different width.

ConversationPanelThreadPanel
Showsa channel or DMreplies to one message
Headerchannel identity, status, tabsthe thread's title and reply count
Has an origin messagenoyes — ThreadPanelOriginalMessage

ThreadPanel carries thread-specific parts that have no equivalent in the conversation column: ThreadPanelOriginalMessage, ThreadPanelReplyCount, ThreadPanelReplySummary, and an in-thread search (ThreadPanelSearch, ThreadPanelSearchCount, ThreadPanelSearchTrailing).

ConversationPanel keeps its own root, body, tabs, and empty state (ConversationPanelRoot, ConversationPanelBody, ConversationPanelContent, ConversationPanelFooter, ConversationPanelTabs, ConversationPanelEmptyState with …Brand / …BrandMark), but its header uses the shared PanelHeader chrome — see layout.md.

Use ConversationPanelEmptyState rather than the generic EmptyState inside a conversation.


These are product-shaped presentation surfaces, not state managers.

SurfaceComposeKeep in the app
Activity inboxActivityInboxPanel, sidebar, controls, selection-toolbar, viewport, and loading-more partsfilters, selection, bulk actions, pagination, loading policy
Reusable inbox rowInboxItem + InboxItemTrigger, content, metadata, trailing, and action partsentity data, read/unread state, navigation, permissions
Workspace searchSearchShellRoot, filters, viewport, result sections and listsquery, filters, fetching, ranking, result selection
Search resultSearchEntityResult*, SearchMessageResult*, or SearchThreadResult*result data, destination, domain-specific copy

Do not substitute Combobox for SearchShellRoot; it is a value picker.

InboxItemTrigger is the row's primary action and inherits the root's disabled state. Compose
secondary controls inside InboxItemActions; do not put navigation policy or bulk-selection state
into the component.

Search result parts own presentation and selected styling, not search semantics. Pick the result
family that matches the rendered entity rather than switching one result component with a kind
prop.


Composer vs Textarea

Textarea is a form control. Composer is a message-sending surface.

Use the ComposerRoot / Composer family whenever the input sends a message. It provides the attachment, drop-overlay, toolbar, submit, and suggestion parts.

Use Textarea for a description field in a form.


Deeply parted by design. Render the parts you need; do not pass item config objects.

Naming is regular at three levels — guess by pattern, then confirm against the declarations (lookup.md):

LevelPrefixAnchor parts
FrameSidebar*SidebarRoot, SidebarHeader, SidebarBody, SidebarLiveActivity
SectionSidebarSection*…Header, …Title, …Count, …Chevron, …Disclosure, …Empty
RowSidebarItem*…Icon, …Title, …Subtitle, …Meta, …Count, …Status
Footer menuSidebarMenu*…Icon, …Title, …Count, …Aside
  • Unread counts go in SidebarItemCount, not appended to the title string.
  • Presence goes in SidebarItemStatus, not a hand-placed dot.
  • SidebarGroupLabel labels a group of sections; SidebarSectionTitle labels one section.
  • SidebarLiveActivity is the slot in the sidebar frame; the standalone strip is LiveAgentActivityBar with …Row / …Avatar / …Status / …Text parts.
  • SidebarItem draws its own active treatment, a yellow sticker; it has no variant. SidebarItemCount is always the pink sticker and SidebarItemIndicator the yellow @; neither takes variant or appearance.

Message surfaces

The message family is large and satellite-structured: MessageItem is the anchor, and each concern is its own part family with a regular prefix. Do not hand-roll any of these — every state below already has a dedicated part.

PrefixOwns
MessageList*the scroller: MessageListDateDivider, …Notice, …HistoryLimited
MessageItem*one message: …Header, …Sender, …Time, …Body, …Footer, …Gutter, …Toolbar, …ReadReceipt, …SavedIndicator, …SearchMatch
MessageReaction*reaction chips, quick row, picker, tooltip
MessageReference*reference chips: …Chip, …Pending, …Unavailable
MessageAttachment*attachment cards, diff summaries; plus MessageImageGallery* and MessageVideoPreview*
MessageEmbed*link embeds
MessageActionCard*agent action cards; fold resolved cards with defaultOpen={false}; agent reason = HintLabel + Hint; Raft's note = Notice
MessageQuotedPreview*quoted message preview, including …Loading and …Unavailable
MessageReplies*the reply summary row under a message
MessageForwardedBundle*forwarded message bundles
MessageItemTranslation*translation status, original text, retry
MessageMultiSelect*multi-select checkboxes and toolbar
MessageTextSelectionToolbar*the toolbar that appears over selected text

SystemMessage is separate from MessageItem — join/leave/rename events use SystemMessage + SystemMessageTime, and consecutive events collapse with the SystemMessageGroup* disclosure parts.

Product-state parts follow the library-wide pattern: the consumer decides which state part to render (MessageReferencePending, MessageQuotedPreviewUnavailable, MessageItemTranslationFailedStatus), the root never takes a state prop.

Pass a number as MessageReactionCount children to get its built-in 180ms bump on value changes. It skips the initial render and respects reduced motion. Keep the part mounted with a stable key; do not add an application-side animation or key it by the count. Non-numeric children remain supported as static content.

For message text content, the Markdown* renderer parts (see display.md) style each markdown node.


Composer structure

ComposerRoot
  ComposerAttachments → ComposerAttachment → ComposerAttachmentImage | ComposerAttachmentFile,
                        ComposerAttachmentBody, ComposerAttachmentTitle,
                        ComposerAttachmentMeta, ComposerAttachmentRemove,
                        ComposerAttachmentUploadingOverlay,
                        ComposerAttachmentUploadProgressBar,
                        ComposerAttachmentFailedOverlay
  ComposerInput
  ComposerToolbar → ComposerToolbarLabel, ComposerIconButton
  ComposerActions → ComposerSubmit
  ComposerMeta
  ComposerDropOverlay → ComposerDropOverlayLabel, ComposerDropOverlayPrompt
  • ComposerSubmit is a Button locked to variant="accent" — the core-action color. Its props omit variant; do not try to restyle it into a yellow or default button.
  • Upload state has dedicated overlay parts. Render ComposerAttachmentUploadingOverlay while uploading and ComposerAttachmentFailedOverlay on failure — do not dim the tile with a className.
  • Reorderable attachments use SortableComposerAttachments / SortableComposerAttachment / SortableComposerAttachmentsScroll.
  • Mentions and slash commands use the useComposerSuggestions hook with ComposerSuggestionList, ComposerSuggestionGroup, ComposerSuggestionOption, ComposerSuggestionIcon, ComposerSuggestionTitle, ComposerSuggestionMeta, ComposerSuggestionAside.

Set ComposerSuggestionIcon variant to avatar, framed, or auxiliary, and set ComposerSuggestionMeta variant to text or code. Pick avatar for people, framed for channels and commands, and code for anything the user types literally.

The suggestion parts are styling slots, not a combobox state machine. useComposerSuggestions owns matching and keyboard behavior; the parts render it.