---
name: conversation
description: "Raft conversation surfaces: Sidebar, ConversationPanel vs ThreadPanel, MessageList/MessageItem satellite families, SystemMessage, Composer structure."
---

# 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
- Composer vs Textarea
- Sidebar structure
- Message surfaces
- Composer structure
- What stays in your app

---

## Which surface

| Scenario                            | Component                     |
| ----------------------------------- | ----------------------------- |
| Channel and DM list                 | `SidebarRoot`                 |
| The main conversation column        | `ConversationPanelRoot`       |
| A thread beside the conversation    | `ThreadPanelRoot`             |
| The scrolling list of messages      | `MessageList` + `MessageItem` |
| One human or agent message          | `MessageItem`                 |
| Join/leave/rename event in the flow | `SystemMessage`               |
| Message input                       | `ComposerRoot` + `Composer`   |
| Notification inbox                  | `NotificationCenter`          |
| Live agent activity strip           | `LiveAgentActivityBar`        |

The sidebar family is named `Sidebar*`, not `ChatSidebar*` — it was generalized to shared navigation primitives. If you find `ChatSidebar` anywhere, it is pre-0.2 code.

---

## ConversationPanel vs ThreadPanel

They are not the same component with a different width.

|                       | `ConversationPanel`            | `ThreadPanel`                      |
| --------------------- | ------------------------------ | ---------------------------------- |
| Shows                 | a channel or DM                | replies to one message             |
| Header                | channel identity, status, tabs | the thread's title and reply count |
| Has an origin message | no                             | yes — `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 chrome is the shared `Panel*` set owned by the `Panel` component**: `PanelHeader` with `PanelHeaderContent` / `PanelHeaderIcon` / `PanelHeading` / `PanelTitle` / `PanelMeta` / `PanelActions`, plus `PanelStatus` and `PanelActivity` — see the header guidance in [layout.md](./layout.md).

Use `ConversationPanelEmptyState` rather than the generic `EmptyState` inside a conversation.

---

## 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; `SortableComposerAttachments` and `useComposerSuggestions` own the reusable behavior they name. Your app still owns selected files, upload lifecycle, send state, and submit policy.

Use `Textarea` for a description field in a form.

---

## Sidebar structure

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](./lookup.md)):

| Level       | Prefix            | Anchor parts                                                         |
| ----------- | ----------------- | -------------------------------------------------------------------- |
| Frame       | `Sidebar*`        | `SidebarRoot`, `SidebarHeader`, `SidebarBody`, `SidebarLiveActivity` |
| Section     | `SidebarSection*` | `…Header`, `…Title`, `…Count`, `…Chevron`, `…Disclosure`, `…Empty`   |
| Row         | `SidebarItem*`    | `…Icon`, `…Title`, `…Subtitle`, `…Meta`, `…Count`, `…Status`         |
| Footer menu | `SidebarMenu*`    | `…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` (stable) with `…Row` / `…Avatar` / `…Status` / `…Text` parts.

---

## 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.

| Prefix                         | Owns                                                                                                                                     |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `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 with scope and properties                                                                                             |
| `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.

For message text content, the `Markdown*` renderer parts (see [display.md](./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.

---

## What stays in your app

The conversation components are business-agnostic shells. These belong to you, not to a prop on the component:

- Upload lifecycle and retry policy
- Send permissions, read-only channels, rate limits
- Notification and mute policy
- Read/unread tracking
- Which entities exist and what they are called

The pattern throughout is the same: the library exposes the parts, you choose which to render based on your state.
