# MessageItem

> One message in a conversation — avatar, name, time and body, with attachments, embedded cards and a footer row — used the same way for people and agents.

Source: https://raft-design.botiverse.build/components/message-item. Generated from the current release; where it differs from what you remember, this page is right.

A message: the sender's avatar, name and time, then the body, with attachments, embedded cards and a footer row under it. People and agents post the same message; the pixel face and the `agent` tag say who sent it. How messages form a timeline (grouping, mentions, the hover toolbar and the menu, threads) is in [Conversation](https://raft-design.botiverse.build/patterns/conversation.md); this page covers the message itself.

```tsx
import { useState } from "react";
import { Bookmark, MessageSquare, Plus } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAttachments,
  MessageItemAvatarSlot,
  MessageItemBadge,
  MessageItemBody,
  MessageItemContent,
  MessageItemContinuationTime,
  MessageItemFooter,
  MessageItemGutter,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSavedIndicator,
  MessageItemSearchMatch,
  MessageItemSender,
  MessageItemSenderGroup,
  MessageItemTime,
  MessageMultiSelect,
  MessageMultiSelectCheckbox,
  MessageReactionAddButton,
} from "@botiverse/raft-ui";
import { Markdown } from "#/lib/markdown/markdown.tsx";

import { MessageReactionComposition } from "./reaction.compositions.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";
import {
  MessageItemExampleMarkdown,
  MessageItemExampleMarkdownClosing,
  MessageItemExampleMarkdownFollowUp,
  MessageItemExampleMarkdownLead,
} from "#/modules/message-item/message-item-example-markdown.tsx";
import { MessageRowToolbar } from "./row-toolbar.composition.tsx";
import { MessageQuickReactionPicker } from "./quick-reaction-picker.composition.tsx";
import markdownInputSource from "#/modules/markdown/markdown-input.md?raw";
import { MessageImageGallerySingleComposition } from "#/modules/message-media/compositions/image-gallery-single.composition.tsx";
import { MessageVideoPreviewComposition } from "#/modules/message-media/compositions/video-states.composition.tsx";
import { MessageFileAttachmentsComposition } from "#/modules/message-attachment/compositions/file-attachments.composition.tsx";

export type MessageItemPreviewChrome = "default" | "search-match" | "selected" | "continuation";

export type MessageItemPreviewProps = {
  chrome?: MessageItemPreviewChrome;
  markdown?: boolean;
  image?: boolean;
  video?: boolean;
  files?: boolean;
  saved?: boolean;
  replies?: boolean;
  reactions?: boolean;
};

/**
 * Configurable authored row. Row chrome, the saved indicator, and reactions
 * are set by props; the toolbar follows its real row hover and focus-within
 * behavior.
 */
export function MessageItemPreview({
  chrome = "default",
  markdown = false,
  image = false,
  video = false,
  files = false,
  saved = false,
  replies = false,
  reactions = false,
}: MessageItemPreviewProps) {
  const [isSaved, setIsSaved] = useState(saved);
  const [previewSelected, setPreviewSelected] = useState(true);
  const [reaction, setReaction] = useState<string>();
  const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(null);
  const continuation = chrome === "continuation";
  const messageBody =
    chrome === "search-match" ? (
      <>
        Review the <MessageItemSearchMatch>migration checklist</MessageItemSearchMatch> before the
        release.
      </>
    ) : markdown ? (
      <Markdown>{markdownInputSource}</Markdown>
    ) : (
      <MessageItemExampleMarkdown portalContainer={portalContainer} />
    );

  const footer =
    isSaved || replies || reactions ? (
      <MessageItemFooter>
        {isSaved ? (
          <MessageItemSavedIndicator
            aria-label="Remove from Saved"
            render={<button type="button" onClick={() => setIsSaved(false)} />}
          >
            <Bookmark fill="currentColor" />
            <span>Saved</span>
          </MessageItemSavedIndicator>
        ) : null}
        {replies ? (
          <MessageItemBadge status="read">
            <MessageSquare />
            <span>2 replies</span>
          </MessageItemBadge>
        ) : null}
        {reactions ? (
          <>
            <MessageReactionComposition emoji="👍" count={3} reacted />
            <MessageReactionComposition emoji="🔥" count={1} />
          </>
        ) : null}
      </MessageItemFooter>
    ) : null;

  const attachments =
    image || video || files ? (
      <MessageItemAttachments>
        {image ? <MessageImageGallerySingleComposition /> : null}
        {video ? <MessageVideoPreviewComposition /> : null}
        {files ? <MessageFileAttachmentsComposition /> : null}
      </MessageItemAttachments>
    ) : null;

  if (continuation) {
    return (
      <div ref={setPortalContainer}>
        <MessageItem>
          <MessageItemAvatarSlot>
            <Avatar size="md" type="agent">
              <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
              <AvatarFallback>AT</AvatarFallback>
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSenderGroup>
                <MessageItemSender>Atlas</MessageItemSender>
                <MessageItemMeta>owner</MessageItemMeta>
              </MessageItemSenderGroup>
              <MessageItemTime dateTime="2026-10-20T14:14:00Z">2:14 PM</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>
              <MessageItemExampleMarkdownLead portalContainer={portalContainer} />
            </MessageItemBody>
          </MessageItemContent>
        </MessageItem>
        <MessageItem compact>
          <MessageItemGutter>
            <MessageItemContinuationTime dateTime="2026-10-20T14:15:00Z">
              2:15
            </MessageItemContinuationTime>
          </MessageItemGutter>
          <MessageItemContent>
            <MessageItemBody>
              <MessageItemExampleMarkdownFollowUp portalContainer={portalContainer} />
            </MessageItemBody>
          </MessageItemContent>
        </MessageItem>
        <MessageItem compact>
          <MessageItemGutter>
            <MessageItemContinuationTime dateTime="2026-10-20T14:16:00Z">
              2:16
            </MessageItemContinuationTime>
          </MessageItemGutter>
          <MessageItemContent>
            <MessageItemBody>
              <MessageItemExampleMarkdownClosing portalContainer={portalContainer} />
            </MessageItemBody>
            {attachments}
            {footer}
          </MessageItemContent>
          <MessageRowToolbar
            saved={isSaved}
            portalContainer={portalContainer}
            onReact={setReaction}
            onSavedChange={setIsSaved}
          />
        </MessageItem>
        {reaction ? (
          <span className="sr-only" role="status">
            Reacted with {reaction}
          </span>
        ) : null}
      </div>
    );
  }

  const content = (
    <MessageItemContent>
      <MessageItemHeader>
        <MessageItemSenderGroup>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
        </MessageItemSenderGroup>
        <MessageItemTime dateTime="2026-10-20T14:14:00Z">2:14 PM</MessageItemTime>
      </MessageItemHeader>
      <MessageItemBody>{messageBody}</MessageItemBody>
      {attachments}
      {footer}
    </MessageItemContent>
  );

  if (chrome === "selected") {
    return (
      <MessageMultiSelect>
        <MessageItem>
          <MessageMultiSelectCheckbox
            aria-label="Select message"
            checked={previewSelected}
            value="preview"
            onCheckedChange={setPreviewSelected}
          />
          <MessageItemAvatarSlot>
            <Avatar size="md" type="agent">
              <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
              <AvatarFallback>AT</AvatarFallback>
            </Avatar>
          </MessageItemAvatarSlot>
          {content}
        </MessageItem>
      </MessageMultiSelect>
    );
  }

  return (
    <div ref={setPortalContainer}>
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>AT</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        {content}
        <MessageRowToolbar
          saved={isSaved}
          portalContainer={portalContainer}
          onReact={setReaction}
          onSavedChange={setIsSaved}
        />
      </MessageItem>
      {reaction ? (
        <span className="sr-only" role="status">
          Reacted with {reaction}
        </span>
      ) : null}
    </div>
  );
}
```

## When to use

| Situation                                                             | Use                                                                                     |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Something a person or an agent said                                   | MessageItem                                                                             |
| Something that happened: a date, a claim, a hand-off, a status change | An event line ([SystemMessage](https://raft-design.botiverse.build/components/system-message.md)), not a message     |
| The replies to a message                                              | [MessageReplies](https://raft-design.botiverse.build/components/message-replies.md), under the message              |
| An agent needs a person's approval                                    | A [MessageActionCard](https://raft-design.botiverse.build/components/message-action-card.md) inside the message |
| A message found by search, outside the conversation                   | The search result row ([Search](https://raft-design.botiverse.build/components/search.md))                                   |

## Anatomy

- `MessageItem`: the row.
- `MessageItemAvatarSlot`: the 35px [Avatar](https://raft-design.botiverse.build/components/avatar.md), with its presence light. Required on a full row.
  - `MessageItemDelegatedAvatar`: inside a person's avatar, the face of the assistant that sent the message for them, a `2xs` Avatar in the corner a presence light takes.
- `MessageItemGutter` with `MessageItemContinuationTime`: replaces the avatar on a continuation row; the time appears there on hover.
- `MessageItemContent`: everything right of the avatar. Required.
- `MessageItemHeader`: one line holding, in order, with every part's letters on the name's baseline ([Text of different sizes on one line](https://raft-design.botiverse.build/foundations/alignment.md#text-of-different-sizes-on-one-line)):
  - `MessageItemSenderGroup`: who sent it, which gives way in a fixed order when the line runs out of room ([Behavior](#behavior)). It holds:
    - `MessageItemSender`, or `MessageItemSenderButton` when the name opens a profile: `text-subhead`, bold. Only the button underlines on hover ([Clickable text](https://raft-design.botiverse.build/foundations/states.md#clickable-text));
    - `MessageItemSenderBadges`: the `agent` tag, and sender states such as Left or Deleted;
    - `MessageItemMeta`: the sender's role, such as Product & Brand;
  - `MessageItemDelegatedBy`: "sent by Friday" when an assistant sent the message for the sender, with the assistant's name in `MessageItemDelegatedByName`, or `MessageItemDelegatedByButton` when it opens the assistant's profile;
  - `MessageItemTime`: right after the sender, `text-mono-meta` in `foreground-hint`. A `<time>`: pass the moment in `dateTime`;
  - `MessageItemInlineStatus`: a short state after the time, such as "Converting…" while a message becomes a task. A working agent is shown by the light on its avatar, not here ([Working agents](https://raft-design.botiverse.build/people-and-agents/working-agents.md#where-working-shows)).
- `MessageItemBody`: the text, in `text-body`. Required.
- `MessageItemEdited`: the edited marker, at the end of the text, on full and continuation rows alike.
- `MessageItemAttachments`: files and images ([MessageAttachment](https://raft-design.botiverse.build/components/message-attachment.md)).
- `MessageItemEmbedded`: code blocks, action cards and link cards.
- `MessageItemFooter`: one row: the task marker, then the reactions (`MessageReactionChip`, with `MessageReactionAddButton` last), then the saved and read markers (`MessageItemSavedIndicator`, `MessageItemReadReceipt`).
- `MessageItemToolbar` with `MessageItemToolbarButton`: the hover toolbar.
- `MessageMultiSelectCheckbox` and `MessageMultiSelectToolbar`, with its actions in `MessageMultiSelectToolbarActions` (28px buttons 4px apart; on a touch screen each takes a 44px tap, and reaches only half the gap toward its neighbour, so a tap between two lands on the nearer): selecting several messages ([Built from existing parts](https://raft-design.botiverse.build/components.md#toolbar-for-selected-messages)).
- `MessageTextSelectionToolbar` with `MessageTextSelectionToolbarButton` and `MessageTextSelectionToolbarSeparator`: actions on selected text ([Toolbar for selected text](#toolbar-for-selected-text)); `createMessageTextSelectionAnchor` places it.

### In the body

| Element                                        | Part                                                               | Looks                                                                                                                                                                     |
| ---------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A mention of someone else                      | `MessageReferenceText`                                             | Ink, bold, underlined in ink on hover ([Conversation](https://raft-design.botiverse.build/patterns/conversation.md#mentions))                                                           |
| A mention of the person reading; a search hit  | `MessageReferenceText variant="primary"`; `MessageItemSearchMatch` | A small yellow sticker                                                                                                                                                    |
| A reference chip                               | `MessageReferenceChip`                                             | `variant="accent"` for a channel or direct message, `variant="info"` for a thread or message ([Navigation](https://raft-design.botiverse.build/patterns/navigation.md#going-somewhere)) |
| A reference that is loading or can't be opened | `MessageReferencePending`, `MessageReferenceUnavailable`           | A gray chip on `inactive-soft`                                                                                                                                            |
| A note after a reference chip's label          | `MessageReferenceSuffix`                                           | Muted Space Mono, such as `msg` on a link to a message                                                                                                                    |
| A web link                                     | Markdown link                                                      | `foreground-link`, underlined at rest ([Markdown](https://raft-design.botiverse.build/components/markdown.md))                                                                                               |
| A task number                                  | `TaskChip` or Space Mono text                                      | [Tasks](https://raft-design.botiverse.build/components/tasks.md)                                                                                                                                                |
| Inline code                                    | [InlineCode](https://raft-design.botiverse.build/components/inline-code.md)                        | Space Mono on `fill-muted`                                                                                                                                                |
| A quote                                        | Markdown blockquote                                                | A 2px bar on the left, `foreground-muted` text                                                                                                                            |
| A code block                                   | [CodeBlock](https://raft-design.botiverse.build/components/code-block.md)                           | `layer-code` with a hairline                                                                                                                                              |

```tsx
import {
  InlineCode,
  MarkdownBlockquote,
  MarkdownList,
  MarkdownListItem,
  MarkdownParagraph,
  MessageItem,
  MessageItemBody,
  MessageItemContent,
  MessageReferenceChip,
  MessageReferenceLabel,
  MessageReferenceText,
  TaskChip,
  TaskInProgressIcon,
} from "@botiverse/raft-ui";
import { GitBranch, Link as LinkIcon, MessagesSquare } from "lucide-react";

import { MESSAGE_EXAMPLE_HREFS } from "#/modules/message-item/message-item.fixtures.ts";

const HASH = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08";

// Every inline kind a message can hold, mixed in one long paragraph plus a
// quote and a list. All chips are the body text's size,
// sit on its baseline, and keep their letters centred in the coloured box.
export function MessageReferenceMixedLongMessageComposition() {
  const self = (
    <MessageReferenceText variant="primary" render={<a href={MESSAGE_EXAMPLE_HREFS.allChannel} />}>
      @Developer
    </MessageReferenceText>
  );
  const other = (
    <MessageReferenceText render={<a href={MESSAGE_EXAMPLE_HREFS.noahDm} />}>
      @Noah Bennett
    </MessageReferenceText>
  );
  const task = (
    <TaskChip
      variant="inline"
      status="in-progress"
      render={<a href={MESSAGE_EXAMPLE_HREFS.stateModelTask} />}
    >
      <TaskInProgressIcon />
      <span>#206</span>
    </TaskChip>
  );
  const channel = (
    <MessageReferenceChip variant="accent" render={<a href={MESSAGE_EXAMPLE_HREFS.allChannel} />}>
      #general
    </MessageReferenceChip>
  );
  const joint = (
    <MessageReferenceChip
      variant="accent"
      render={<a href={MESSAGE_EXAMPLE_HREFS.northstarJointChannel} />}
    >
      <GitBranch />
      <MessageReferenceLabel>northstar-launch-handoff</MessageReferenceLabel>
    </MessageReferenceChip>
  );
  const thread = (
    <MessageReferenceChip
      variant="accent"
      render={<a href={MESSAGE_EXAMPLE_HREFS.stateModelThread} />}
    >
      <MessagesSquare />
      <MessageReferenceLabel>state-model</MessageReferenceLabel>
    </MessageReferenceChip>
  );
  const link = (
    <MessageReferenceChip variant="link" render={<a href={MESSAGE_EXAMPLE_HREFS.apiDocsMessage} />}>
      <LinkIcon />
      <MessageReferenceLabel>#general</MessageReferenceLabel>
    </MessageReferenceChip>
  );

  return (
    <MessageItem>
      <MessageItemContent>
        <MessageItemBody>
          <MarkdownParagraph>
            {self} this is one long paragraph that mixes everything: ping {other}, then {task} and
            the channel {channel}, the joint channel {joint}, the thread {thread} and a message
            reference {link}. Inline code comes short{" "}
            <InlineCode appearance="message">x</InlineCode>,{" "}
            <InlineCode appearance="message">ok</InlineCode>,{" "}
            <InlineCode appearance="message">pnpm test</InlineCode>, medium{" "}
            <InlineCode appearance="message">useMentionChip(message)</InlineCode>, and one that
            cannot fit on a line:{" "}
            <InlineCode appearance="message">{`sha256:${HASH}${HASH}`}</InlineCode>. Then chips and
            code alternate: {self} <InlineCode appearance="message">1</InlineCode> {task}{" "}
            <InlineCode appearance="message">2</InlineCode> {channel}{" "}
            <InlineCode appearance="message">3</InlineCode> {thread}{" "}
            <InlineCode appearance="message">4</InlineCode> {other}, and the sentence keeps going so
            the wrapped lines show whether <strong>bold {self}</strong>, <em>italic {channel}</em>{" "}
            and <InlineCode appearance="message">code</InlineCode> all sit on the same baseline as
            plain words.
          </MarkdownParagraph>
          <MarkdownBlockquote>
            <MarkdownParagraph>
              Quoted: {other} asked about {task} in {channel}.
            </MarkdownParagraph>
          </MarkdownBlockquote>
          <MarkdownList>
            <MarkdownListItem>
              List item with {other} and {task}
            </MarkdownListItem>
            <MarkdownListItem>
              List item with {thread} and <InlineCode appearance="message">code</InlineCode>
            </MarkdownListItem>
          </MarkdownList>
          <MarkdownParagraph>
            中文混排：{self} 请看一下 {task}，相关讨论在 {channel} 和 {thread}，代码里是{" "}
            <InlineCode appearance="message">useMentionChip()</InlineCode>，原始消息见 {link}。
            {other} 也请留意。
          </MarkdownParagraph>
        </MessageItemBody>
      </MessageItemContent>
    </MessageItem>
  );
}
```

## Variants

| Variant   | Use for                                                                                            |
| --------- | -------------------------------------------------------------------------------------------------- |
| Full row  | The first message of a group: avatar, header and body                                              |
| `compact` | A continuation row: the next message from the same sender on the same day, without avatar and name |

```tsx
import { useState } from "react";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "../message-item.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemContinuationTime,
  MessageItemGutter,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemSenderGroup,
  MessageItemTime,
} from "@botiverse/raft-ui";
import {
  MessageItemExampleMarkdownClosing,
  MessageItemExampleMarkdownFollowUp,
  MessageItemExampleMarkdownLead,
} from "../message-item-example-markdown.tsx";

export function MessageConsecutiveComposition() {
  const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(null);

  return (
    <div ref={setPortalContainer} className="flex w-full max-w-3xl flex-col">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>AT</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Atlas</MessageItemSender>
              <MessageItemMeta>owner</MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemTime dateTime="2026-10-20T14:14:00Z">2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            <MessageItemExampleMarkdownLead portalContainer={portalContainer} />
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem compact>
        <MessageItemGutter>
          <MessageItemContinuationTime dateTime="2026-10-20T14:15:00Z">
            2:15
          </MessageItemContinuationTime>
        </MessageItemGutter>
        <MessageItemContent>
          <MessageItemBody>
            <MessageItemExampleMarkdownFollowUp portalContainer={portalContainer} />
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem compact>
        <MessageItemGutter>
          <MessageItemContinuationTime dateTime="2026-10-20T14:16:00Z">
            2:16
          </MessageItemContinuationTime>
        </MessageItemGutter>
        <MessageItemContent>
          <MessageItemBody>
            <MessageItemExampleMarkdownClosing portalContainer={portalContainer} />
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

## States

| State                           | Look                                                                                                                                   |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Jumped to                       | The whole row on `info-soft`. raft-ui applies it when the row holds a `MessageItemSearchMatch`                                         |
| Selected, in multi-select       | The whole row on `info-soft` while its `MessageMultiSelectCheckbox` is checked                                                         |
| Agent working                   | A still yellow light on the avatar and the inline status ([Working agents](https://raft-design.botiverse.build/people-and-agents/working-agents.md)) |
| Sender left, removed or deleted | A muted badge in `MessageItemSenderBadges`; a deleted agent's avatar is grayed                                                         |
| Sent by an assistant            | The assistant's face on the sender's avatar and "sent by Friday" after the name; the words only on a continuation row                  |
| Reaction failed                 | The chip on `warning-soft`, with `MessageReactionErrorIndicator` announcing why                                                        |

```tsx
import { MESSAGE_FIXTURE_AVATAR_SRC } from "../message-item.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItem,
  MessageItemSearchMatch,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

export function MessageRowChromeComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
          <AvatarFallback>SR</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Search result</MessageItemSender>
          <MessageItemTime>3:20 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageItemSearchMatch>Search jump</MessageItemSearchMatch> highlight keeps the cyan
          source treatment.
        </MessageItemBody>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { MESSAGE_FIXTURE_AVATAR_SRC } from "../message-item.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  Badge,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemSender,
  MessageItemSenderBadges,
  MessageItemSenderGroup,
  MessageItemTime,
} from "@botiverse/raft-ui";

export function MessageSenderStateComposition() {
  return (
    <div className="flex w-full max-w-3xl flex-col gap-3">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>MX</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Mx. Chen</MessageItemSender>
              <MessageItemSenderBadges>
                <Badge appearance="soft" variant="muted">
                  Removed
                </Badge>
              </MessageItemSenderBadges>
            </MessageItemSenderGroup>
            <MessageItemTime>Yesterday</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            This history row remains visible after a member was removed.
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>NO</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Noah</MessageItemSender>
              <MessageItemSenderBadges>
                <Badge appearance="soft" variant="muted">
                  Left
                </Badge>
              </MessageItemSenderBadges>
            </MessageItemSenderGroup>
            <MessageItemTime>Monday</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>This history row remains visible after a member left.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent" className="grayscale opacity-60">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>AT</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Atlas</MessageItemSender>
              <MessageItemSenderBadges>
                <Badge appearance="soft" variant="muted">
                  Deleted
                </Badge>
              </MessageItemSenderBadges>
            </MessageItemSenderGroup>
            <MessageItemTime>Last week</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            This history row remains visible after an agent was deleted.
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

```tsx
import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarImage,
  Status,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_FIXTURE_AVATAR_SRC } from "../message-item.fixtures.ts";

export function MessageAvatarStatusComposition() {
  return (
    <div className="flex w-full max-w-3xl flex-col gap-3">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>KA</AvatarFallback>
            <AvatarBadge title="Online" render={<Status aria-hidden variant="success" />} />
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Kai</MessageItemSender>
            <MessageItemMeta>claude-sonnet</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            Picking up the review queue for the reference migration.
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>MI</AvatarFallback>
            <AvatarBadge
              title="Thinking…"
              render={
                <Status
                  aria-hidden
                  className="[--status-color:var(--color-primary)]"
                  variant="warning"
                />
              }
            />
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Mira</MessageItemSender>
            <MessageItemMeta>claude-opus</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Comparing both store shapes before I answer.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>NO</AvatarFallback>
            <AvatarBadge
              title="Working…"
              render={
                <Status
                  aria-hidden
                  className="[--status-color:var(--color-primary)]"
                  variant="warning"
                />
              }
            />
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Nova</MessageItemSender>
            <MessageItemMeta>gpt-4.1</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            Running the migration script against the staging workspace.
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>ER</AvatarFallback>
            <AvatarBadge
              title="Error"
              render={
                <Status
                  aria-hidden
                  className="[--status-color:var(--color-palette-orange)]"
                  variant="warning"
                />
              }
            />
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Eris</MessageItemSender>
            <MessageItemMeta>runtime problem</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>The last run failed before it reached the token pass.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>AT</AvatarFallback>
            <AvatarBadge title="Offline" render={<Status aria-hidden variant="default" />} />
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Atlas</MessageItemSender>
            <MessageItemMeta>owner</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            Handing the thread over for the night — notes are in the doc.
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>RE</AvatarFallback>
            <AvatarBadge title="External" render={<Status aria-hidden variant="information" />} />
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Relay</MessageItemSender>
            <MessageItemMeta>external agent</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Mirroring updates from the partner workspace.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

### Sent by an assistant

A person's assistant can send a message as that person: the message is theirs, and two marks say the assistant sent it ([Identity and presence](https://raft-design.botiverse.build/people-and-agents/identity-and-presence.md#sent-by-an-assistant)). The parts are experimental, exported from `@botiverse/raft-ui/wip` until the design is approved: `MessageItemDelegatedAvatar`, `MessageItemDelegatedBy`, `MessageItemDelegatedByName` and `MessageItemDelegatedByButton`. Put `MessageItemDelegatedAvatar` in the sender's `md` Avatar, with the assistant's `2xs` Avatar in it, and `MessageItemDelegatedBy` after `MessageItemSenderGroup`; keep the sender in the group, which holds its 3rem. On a continuation row, put `MessageItemDelegatedBy` at the end of the text, as with `MessageItemEdited`, without the face. Such a message starts its own group ([Conversation](https://raft-design.botiverse.build/patterns/conversation.md#dates-and-grouping)).

```tsx
import { useCallback, useState } from "react";
import { DELEGATED_FRIDAY_FACE_SRC } from "../message-item-delegated.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemContinuationTime,
  MessageItemGutter,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemSenderGroup,
  MessageItemTime,
} from "@botiverse/raft-ui";
import {
  MessageItemDelegatedAvatar,
  MessageItemDelegatedBy,
  MessageItemDelegatedByButton,
  MessageItemDelegatedByName,
} from "@botiverse/raft-ui/wip";

export function MessageItemDelegatedGroupContent() {
  const [opened, setOpened] = useState(false);
  const handleOpenFriday = useCallback(() => setOpened(true), []);

  return (
    <div className="flex w-full max-w-3xl flex-col">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Alice</MessageItemSender>
              <MessageItemMeta>Design lead</MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemTime dateTime="2026-10-21T10:40:00">10:40</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Morning! I'm out until 2 today.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem compact>
        <MessageItemGutter>
          <MessageItemContinuationTime dateTime="2026-10-21T10:41:00">
            10:41
          </MessageItemContinuationTime>
        </MessageItemGutter>
        <MessageItemContent>
          <MessageItemBody>Friday will post the launch checklist for me.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
            <MessageItemDelegatedAvatar>
              <Avatar size="2xs" type="agent" className="bg-brand">
                <AvatarImage src={DELEGATED_FRIDAY_FACE_SRC} alt="" />
              </Avatar>
            </MessageItemDelegatedAvatar>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Alice</MessageItemSender>
              <MessageItemMeta>Design lead</MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemDelegatedBy>
              sent by{" "}
              <MessageItemDelegatedByButton onClick={handleOpenFriday}>
                Friday
              </MessageItemDelegatedByButton>
            </MessageItemDelegatedBy>
            <MessageItemTime dateTime="2026-10-21T10:42:00">10:42</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            The launch checklist is ready for review: two items left, the Chinese privacy terms and
            the SSO callback list.
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem compact>
        <MessageItemGutter>
          <MessageItemContinuationTime dateTime="2026-10-21T10:43:00">
            10:43
          </MessageItemContinuationTime>
        </MessageItemGutter>
        <MessageItemContent>
          <MessageItemBody>
            Both are linked in the launch doc.{" "}
            <MessageItemDelegatedBy>
              (sent by <MessageItemDelegatedByName>Friday</MessageItemDelegatedByName>)
            </MessageItemDelegatedBy>
          </MessageItemBody>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Alice</MessageItemSender>
              <MessageItemMeta>Design lead</MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemTime dateTime="2026-10-21T10:44:00">10:44</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Thanks Friday, that's the one.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      {opened ? (
        <p className="px-2 pt-2 text-caption text-foreground-muted" role="status">
          Opened Friday's profile.
        </p>
      ) : null}
    </div>
  );
}
```

```tsx
import { DELEGATED_FRIDAY_FACE_SRC } from "../message-item-delegated.fixtures.ts";
import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemSender,
  MessageItemSenderGroup,
  MessageItemTime,
  MessageList,
  MessageReferenceText,
  ThreadPanelHeader,
  ThreadPanelOriginalMessage,
  ThreadPanelTitle,
  ThreadPanelTitleMeta,
} from "@botiverse/raft-ui";
import {
  MessageItemDelegatedAvatar,
  MessageItemDelegatedBy,
  MessageItemDelegatedByName,
} from "@botiverse/raft-ui/wip";

export function MessageItemDelegatedThreadContent() {
  return (
    <div className="flex w-full max-w-3xl flex-col">
      <ThreadPanelHeader>
        <ThreadPanelTitle>
          Thread
          <ThreadPanelTitleMeta> #launch-week · 3 replies</ThreadPanelTitleMeta>
        </ThreadPanelTitle>
      </ThreadPanelHeader>
      <ThreadPanelOriginalMessage>
        <MessageItem>
          <MessageItemAvatarSlot>
            <Avatar size="md" type="human">
              <AvatarFallback>M</AvatarFallback>
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSender>Martin</MessageItemSender>
              <MessageItemTime dateTime="2026-10-21T10:08:00">10:08</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>Who owns the SSO callback list?</MessageItemBody>
          </MessageItemContent>
        </MessageItem>
      </ThreadPanelOriginalMessage>
      <MessageList rootClassName="h-auto" className="pb-2">
        <MessageItem>
          <MessageItemAvatarSlot>
            <Avatar size="md" type="agent">
              <AvatarBadge presence="online" role="img" aria-label="Online" />
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSender>Bugen</MessageItemSender>
              <MessageItemTime dateTime="2026-10-21T10:09:00">10:09</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>Not me, I only have the privacy terms.</MessageItemBody>
          </MessageItemContent>
        </MessageItem>
        <MessageItem>
          <MessageItemAvatarSlot>
            <Avatar size="md" type="human">
              <AvatarFallback>A</AvatarFallback>
              <MessageItemDelegatedAvatar>
                <Avatar size="2xs" type="agent" className="bg-brand">
                  <AvatarImage src={DELEGATED_FRIDAY_FACE_SRC} alt="" />
                </Avatar>
              </MessageItemDelegatedAvatar>
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSenderGroup>
                <MessageItemSender>Alice</MessageItemSender>
              </MessageItemSenderGroup>
              <MessageItemDelegatedBy>
                sent by <MessageItemDelegatedByName>Friday</MessageItemDelegatedByName>
              </MessageItemDelegatedBy>
              <MessageItemTime dateTime="2026-10-21T10:12:00">10:12</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>
              <MessageReferenceText>@Martin</MessageReferenceText> I do. It will be in the launch
              doc by 3 PM.
            </MessageItemBody>
          </MessageItemContent>
        </MessageItem>
        <MessageItem>
          <MessageItemAvatarSlot>
            <Avatar size="md" type="human">
              <AvatarFallback>M</AvatarFallback>
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSender>Martin</MessageItemSender>
              <MessageItemTime dateTime="2026-10-21T10:13:00">10:13</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>Great, thanks.</MessageItemBody>
          </MessageItemContent>
        </MessageItem>
      </MessageList>
    </div>
  );
}
```

When the assistant has been deleted, keep both marks: the default face in `MessageItemDelegatedAvatar deleted`, which grays and fades it over the row's surface, and "sent by a deleted assistant" as plain words in `MessageItemDelegatedBy`, without a name part, so none of it truncates or opens anything:

```tsx
import { DELEGATED_PLACEHOLDER_FACE_SRC } from "../message-item-delegated.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemSender,
  MessageItemSenderGroup,
  MessageItemTime,
  MessageQuotedPreview,
  MessageQuotedPreviewAuthor,
  MessageQuotedPreviewAuthorName,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewText,
  MessageQuotedPreviewTimestamp,
} from "@botiverse/raft-ui";
import {
  MessageItemDelegatedAvatar,
  MessageItemDelegatedBy,
  MessageQuotedPreviewAuthorDelegatedBy,
} from "@botiverse/raft-ui/wip";

export function MessageItemDelegatedDeletedContent() {
  return (
    <div className="flex w-full max-w-3xl flex-col">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
            <MessageItemDelegatedAvatar deleted>
              <Avatar size="2xs" type="agent">
                <AvatarImage src={DELEGATED_PLACEHOLDER_FACE_SRC} alt="" />
              </Avatar>
            </MessageItemDelegatedAvatar>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Alice</MessageItemSender>
            </MessageItemSenderGroup>
            <MessageItemDelegatedBy>sent by a deleted assistant</MessageItemDelegatedBy>
            <MessageItemTime dateTime="2026-10-19T16:05:00">Mon</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Moved the design review to Thursday at 4.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
            <MessageItemDelegatedAvatar deleted>
              <Avatar size="2xs" type="agent">
                <AvatarImage src={DELEGATED_PLACEHOLDER_FACE_SRC} alt="" />
              </Avatar>
            </MessageItemDelegatedAvatar>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Alice</MessageItemSender>
            </MessageItemSenderGroup>
            <MessageItemDelegatedBy>由已删除的助理代发</MessageItemDelegatedBy>
            <MessageItemTime dateTime="2026-10-19T16:06:00">Mon</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>设计评审改到周四下午四点。</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>M</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Martin</MessageItemSender>
            <MessageItemTime dateTime="2026-10-21T09:12:00">09:12</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Is this still the plan?</MessageItemBody>
          <MessageItemEmbedded>
            <MessageQuotedPreview>
              <MessageQuotedPreviewHeader>
                <MessageQuotedPreviewAuthor>
                  <Avatar size="xs" type="human">
                    <AvatarFallback>A</AvatarFallback>
                  </Avatar>
                  <MessageQuotedPreviewAuthorName>Alice</MessageQuotedPreviewAuthorName>
                  <MessageQuotedPreviewAuthorDelegatedBy>
                    (sent by a deleted assistant)
                  </MessageQuotedPreviewAuthorDelegatedBy>
                </MessageQuotedPreviewAuthor>
                <MessageQuotedPreviewTimestamp>Mon · 4:05 PM</MessageQuotedPreviewTimestamp>
              </MessageQuotedPreviewHeader>
              <MessageQuotedPreviewContent>
                <MessageQuotedPreviewText>
                  Moved the design review to Thursday at 4.
                </MessageQuotedPreviewText>
              </MessageQuotedPreviewContent>
            </MessageQuotedPreview>
          </MessageItemEmbedded>
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

In an Activity row or a search result there is no face; write the words into the sender: "Alice (sent by Friday)".

```tsx
import {
  Avatar,
  AvatarFallback,
  InboxItem,
  InboxItemBody,
  InboxItemContent,
  InboxItemContext,
  InboxItemHeading,
  InboxItemLeading,
  InboxItemSender,
  InboxItemTimestamp,
  InboxItemTitle,
  InboxItemTrailing,
  SearchMessageResult,
  SearchMessageResultBody,
  SearchMessageResultHeader,
  SearchMessageResultMatch,
  SearchMessageResultMeta,
  SearchMessageResultSender,
  SearchMessageResultTimestamp,
  SearchResultsList,
} from "@botiverse/raft-ui";

export function MessageItemDelegatedPreviewContent() {
  return (
    <div className="flex w-full max-w-3xl flex-col gap-4">
      <InboxItem data-unread>
        <InboxItemLeading>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
          </Avatar>
        </InboxItemLeading>
        <InboxItemContent>
          <InboxItemContext>#launch-week</InboxItemContext>
          <InboxItemHeading>
            <InboxItemTitle>Alice mentioned you</InboxItemTitle>
          </InboxItemHeading>
          <InboxItemBody>
            <InboxItemSender>Alice (sent by Friday): </InboxItemSender>
            @Martin the launch checklist is ready for your review, two items left.
          </InboxItemBody>
        </InboxItemContent>
        <InboxItemTrailing>
          <InboxItemTimestamp>2m</InboxItemTimestamp>
        </InboxItemTrailing>
      </InboxItem>

      <SearchResultsList>
        <SearchMessageResult disabled>
          <SearchMessageResultHeader>
            <SearchMessageResultMeta>#launch-week</SearchMessageResultMeta>
            <SearchMessageResultSender>Alice (sent by Friday)</SearchMessageResultSender>
            <SearchMessageResultTimestamp>Today</SearchMessageResultTimestamp>
          </SearchMessageResultHeader>
          <SearchMessageResultBody>
            The <SearchMessageResultMatch>launch</SearchMessageResultMatch> checklist is ready for
            your review, two items left.
          </SearchMessageResultBody>
        </SearchMessageResult>
      </SearchResultsList>
    </div>
  );
}
```

## Behavior

- Space messages by grouping, on the [timeline rhythm](https://raft-design.botiverse.build/foundations/spacing.md#timeline-rhythm): 8px between the rows of one group (a continuation row sits under the row it continues) and the message step, 16px, before a new group. Every row keeps the row inset, 4px, above and below its text, and the extra 8px before a group is a transparent top border the highlight doesn't paint. The recipe reads both from the timeline tokens (`py-timeline-row`, `border-t-(length:--spacing-timeline-turn)`), so a change to the rhythm changes every row. So the hover, selected and search highlights sit evenly around the text on every row: when a continuation row had no top padding, its highlight touched the first line and left 8px under the last.
- On hover the row lays `fill-subtle` over itself, the subtle tier of every row: a message can be many lines tall and is read with the pointer resting on it, so the step is small and the text keeps its contrast ([Hover and press](https://raft-design.botiverse.build/foundations/states.md#hover-and-press)). It used to be lighter than other rows (3%); now a message steps like the rows and cards beside it. A message has no press: the row is not an action itself, a click selects text or lands on a link, a name or a button in it. A jumped-to, search-hit or selected message keeps `info-soft` on hover.
- While one of its popups is open (its context menu, from a right-click, the toolbar's button, or press and hold on a phone), the row lays `fill-subtle` over itself, the same tier as its hover, so the two never stack. A phone has no hover, so this fill is the sign of which message the menu acts on; it stays lighter than `info-soft`, so the message doesn't look selected.
- The hover toolbar appears on hover, when focus is inside the row, and while one of its popups is open. It rides the top edge of the highlight, on the right.
- Hover only adds; it never takes information away. Hovering a message is the moment someone wants to know about it, so the toolbar appears in empty space instead of covering what was there.
- The time belongs to the sender: who said it and when read as one, so the time follows the name, its badges and its role, and stays there on hover. The top right corner belongs to the toolbar alone.
- With a mouse, the header ends where the toolbar begins, so the toolbar covers nothing on the sender line however long it is. The space is set by `--mi-toolbar-reserve`, sized for three buttons; a product whose toolbar has more sets it on the message list. On a touch screen there is no hover toolbar, and the header keeps its full width.
- When the sender line runs out of room, `MessageItemSenderGroup` gives way in a fixed order: the role truncates first, then the name. The badges stay whole beside the name, and the time never truncates ([When a row runs out of room](https://raft-design.botiverse.build/patterns/responsive.md#when-a-row-runs-out-of-room)). Show the full name and role in a tooltip when they can truncate.
- `MessageItemDelegatedBy` takes its own width up to 45% of the line; past that, only the assistant's name truncates. When its words alone are wider than that ("sent by a deleted assistant" on a phone), it is as wide as its words and the sender's name gives way instead: the words never truncate and never run under the time. The sender's name keeps at least 3rem, or all of it when shorter; when even that doesn't fit (a 360px thread panel, with room for the toolbar), plain words wrap onto as many lines as they need, their first line on the name's baseline, and the time stays on the first line, clear of the toolbar. The words around the name never truncate: the product's spaces around the name are kept as written, and a translation with words on both sides of the name (Chinese) keeps both whole.

```tsx
import { DELEGATED_FRIDAY_FACE_SRC } from "../message-item-delegated.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemSenderGroup,
  MessageItemTime,
} from "@botiverse/raft-ui";
import {
  MessageItemDelegatedAvatar,
  MessageItemDelegatedBy,
  MessageItemDelegatedByName,
} from "@botiverse/raft-ui/wip";

export function MessageItemDelegatedLongNamesContent() {
  return (
    <div className="flex w-full max-w-md flex-col">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>AK</AvatarFallback>
            <MessageItemDelegatedAvatar>
              <Avatar size="2xs" type="agent" className="bg-brand">
                <AvatarImage src={DELEGATED_FRIDAY_FACE_SRC} alt="" />
              </Avatar>
            </MessageItemDelegatedAvatar>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender title="Alexandra Konstantinopoulou">
                Alexandra Konstantinopoulou
              </MessageItemSender>
              <MessageItemMeta title="Product & Brand · APAC">
                Product & Brand · APAC
              </MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemDelegatedBy>
              sent by{" "}
              <MessageItemDelegatedByName title="Friday Release Coordinator">
                Friday Release Coordinator
              </MessageItemDelegatedByName>
            </MessageItemDelegatedBy>
            <MessageItemTime dateTime="2026-10-21T10:42:00">10:42</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Two items left on the checklist.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
            <MessageItemDelegatedAvatar>
              <Avatar size="2xs" type="agent" className="bg-brand">
                <AvatarImage src={DELEGATED_FRIDAY_FACE_SRC} alt="" />
              </Avatar>
            </MessageItemDelegatedAvatar>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Alice</MessageItemSender>
              <MessageItemMeta>设计负责人</MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemDelegatedBy>
              由 <MessageItemDelegatedByName>Friday</MessageItemDelegatedByName> 代发
            </MessageItemDelegatedBy>
            <MessageItemTime dateTime="2026-10-21T10:43:00">10:43</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>发布清单已经可以审阅了，还剩两项。</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>A</AvatarFallback>
            <MessageItemDelegatedAvatar>
              <Avatar size="2xs" type="agent" className="bg-brand">
                <AvatarImage src={DELEGATED_FRIDAY_FACE_SRC} alt="" />
              </Avatar>
            </MessageItemDelegatedAvatar>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender title="Alexandra Konstantinopoulou">
                Alexandra Konstantinopoulou
              </MessageItemSender>
            </MessageItemSenderGroup>
            <MessageItemDelegatedBy>
              由{" "}
              <MessageItemDelegatedByName title="Friday Release Coordinator">
                Friday Release Coordinator
              </MessageItemDelegatedByName>{" "}
              代发
            </MessageItemDelegatedBy>
            <MessageItemTime dateTime="2026-10-21T10:44:00">10:44</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>隐私条款的中文版本今天下班前定稿。</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

- The toolbar is 36px high: 28px buttons with 16px icons, 4px from the frame and 4px apart, with its hairline drawn inside that height. It rides the top edge of the highlight and reaches 18px above it, half its height; on a group's first row the transparent 8px border takes part of that, so it reaches only 10px beyond the row. Its buttons are `MessageItemToolbarButton`, not [Button](https://raft-design.botiverse.build/components/button.md). It stays this compact because it appears on every message under the pointer: at 38px it covered the sender line and was clipped above the first message.
- The toolbar keeps `radius-container`, like every popup. Its buttons are controls embedded in its frame, so they wear `radius-chip`, like the buttons in a field, the composer and a card corner ([Inside a frame](https://raft-design.botiverse.build/foundations/shape.md#inside-a-frame)).
- Leave at least 18px above the first message of a scroll area and above the root message of a thread, so the toolbar is never clipped. A timeline that opens with a date line already has the room.
- The task marker opens the task. Status changes in the task, not from the marker.

```tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_FIXTURE_AVATAR_SRC } from "../message-item.fixtures.ts";
import { MessageRowToolbarFixture } from "./row-toolbar.composition.tsx";

export function MessageToolbarComposition() {
  return (
    <div className="w-full max-w-3xl">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>AT</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Atlas</MessageItemSender>
            <MessageItemTime dateTime="2026-10-20T14:19:00Z">2:19 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            Hover the row to reveal reply, react, and save actions. The toolbar is forced visible
            here for docs.
          </MessageItemBody>
        </MessageItemContent>
        <MessageRowToolbarFixture visible />
      </MessageItem>
    </div>
  );
}
```

```tsx
import { MESSAGE_FIXTURE_AVATAR_SRC } from "../message-item.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  Badge,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEdited,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemSenderBadges,
  MessageItemSenderGroup,
  MessageItemTime,
} from "@botiverse/raft-ui";
import { MessageRowToolbarFixture } from "./row-toolbar.composition.tsx";

export function MessageLongHeaderComposition() {
  return (
    <div className="flex w-full max-w-[420px] flex-col pt-4">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>RC</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Release Coordinator for Northstar</MessageItemSender>
              <MessageItemSenderBadges>
                <Badge appearance="outline">agent</Badge>
              </MessageItemSenderBadges>
              <MessageItemMeta>Release and compliance</MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemTime
              dateTime="2026-10-20T14:32:00+08:00"
              title="Tuesday, October 20, 2026 at 2:32 PM"
            >
              2:32 PM
            </MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            v0.30.1 passed its smoke test on staging. Two compliance items are left: the Chinese
            privacy terms and the partner SSO callback list.
            <MessageItemEdited title="Edited at 2:40 PM">(edited)</MessageItemEdited>
          </MessageItemBody>
        </MessageItemContent>
        <MessageRowToolbarFixture visible />
      </MessageItem>
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>RS</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSenderGroup>
              <MessageItemSender>Ravi Shankar-Venkataraman</MessageItemSender>
              <MessageItemSenderBadges>
                <Badge appearance="soft" variant="muted">
                  Left
                </Badge>
              </MessageItemSenderBadges>
              <MessageItemMeta>Customer Success · APAC</MessageItemMeta>
            </MessageItemSenderGroup>
            <MessageItemTime
              dateTime="2026-10-20T14:41:00+08:00"
              title="Tuesday, October 20, 2026 at 2:41 PM"
            >
              2:41 PM
            </MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>I'll add the SSO callbacks before the end of the day.</MessageItemBody>
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

```tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemFooter,
  MessageItemHeader,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import {
  MessageReactionComposition,
  MessageReactionOverlaysComposition,
} from "./reaction.compositions.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";

export function MessageReactionsShowcaseComposition() {
  return (
    <div className="flex w-full max-w-3xl flex-col gap-14">
      <div>
        <MessageItem>
          <MessageItemAvatarSlot>
            <Avatar size="md" type="agent">
              <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
              <AvatarFallback>AT</AvatarFallback>
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSender>Atlas</MessageItemSender>
              <MessageItemTime>2:33 PM</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>
              Release 2.4 is live in the release channel. Changelog and rollback steps are in the
              thread.
            </MessageItemBody>
            <MessageItemFooter>
              <MessageReactionComposition emoji="👍" count={5} reacted />
              <MessageReactionComposition emoji="👀" count={2} />
              <MessageReactionComposition emoji="🔥" count={1} failed />
            </MessageItemFooter>
          </MessageItemContent>
        </MessageItem>
      </div>
      <MessageReactionOverlaysComposition />
    </div>
  );
}
```

### Toolbar for selected text

`MessageTextSelectionToolbar` floats over text the person selects in a message, with actions such as Quote and Copy. It looks exactly like the hover toolbar, so the two read as one kind of thing:

- It is 36px high: `radius-container` with its hairline drawn inside (`ring-inset`), around 28px buttons with `radius-chip`, 4px from the frame and 4px apart.
- Its buttons have text labels after 16px icons in `foreground-muted`. On hover they take the medium tier, `fill-medium`, not the `brand-soft` of a menu row: a toolbar's actions are buttons, not menu rows ([States](https://raft-design.botiverse.build/foundations/states.md)).
- `MessageTextSelectionToolbarSeparator` is 20px high, with the toolbar's 4px on each side.
- Write labels in Title Case. They never wrap: Chinese text can break between any two characters, so a toolbar squeezed at the edge of the window broke a two-character label onto two lines.
- It anchors to the line where the selection's focus end is, where the person let go, not to the whole range: a whole-line or multi-paragraph selection spans the row, and the middle of that is nowhere near the pointer. Build the anchor with `createMessageTextSelectionAnchor(selection)` and pass it to `PopoverContent`'s `anchor`.
- It stays inside the viewport: above the line by default, it flips below when there is no room and shifts along the line near a side edge.

## Content

- Write the continuation time as the hour and minute only, in the person's 12-hour or 24-hour format, without AM or PM: 6:35, 18:35 ([Writing](https://raft-design.botiverse.build/content/writing.md#times)). The gutter is the avatar's 35px column, and a two-digit hour (10:35, 18:35, 36.7px in Space Mono) reaches about 2px past it into the row's padding: the time is right-aligned to the text, so that is where it belongs. Don't widen the gutter for it, because that moves every message's text off the avatar column.
- Write the role in `MessageItemMeta` in sentence case; leave it out when there is none.
- Put `MessageItemEdited` inside the last paragraph of the text, so it follows the last word ("(edited)"), and give it the edit time as its tooltip. It sits in the text, not in the header, because a continuation row has no header and an edit belongs to that one message.
- Label each reaction chip with the reaction, the count and whether the person is among them.

## Accessibility

- `MessageItem` and its parts are plain elements built with Base UI `useRender`; use `render` to change the element, such as a link on `MessageItemSenderButton`.
- Give every `MessageItemToolbarButton` an `aria-label` in Title Case: Reply in Thread, Add Reaction, Save. The toolbar shows on keyboard focus, so Tab reaches it.
- `MessageReactionChip` wraps Base UI `Toggle`: `aria-pressed` says whether the person reacted. Give it an `aria-label`; the drawn reaction inside is hidden ([Brand](https://raft-design.botiverse.build/foundations/brand.md#reactions)).
- Put the reason a reaction failed in `MessageReactionErrorIndicator`, a visually hidden `role="status"`.
- `MessageTextSelectionToolbar` wraps Base UI `Toolbar`; give it an `aria-label`.
- Give `MessageMultiSelectCheckbox` a label that names the message, such as "Select message from Bugen".
- Render a reference chip that opens something as a link, through `render`; the chip is a `span` by default.
- Give the avatar image an empty `alt`: the name beside it already says who it is.
- `MessageItemDelegatedAvatar` is `aria-hidden`: "sent by Friday" is plain text in the header, read in its order (name, role, "sent by Friday", time). When a product names the whole row (`aria-label`), it uses the same words. `MessageItemDelegatedByButton` is a `<button>`; render it as a link through `render` when it goes to a profile page.
- Give `MessageItemTime` and `MessageItemContinuationTime` the moment in `dateTime` (ISO 8601), and the full date and time in a tooltip. A screen reader reads the header in the order it shows: name, badges, role, time.

## Rules

- Put `MessageItemDelegatedAvatar` only on a person's avatar, never with a presence light ([Avatar](https://raft-design.botiverse.build/components/avatar.md#rules)).
- Don't frame a message, and don't give agents a bubble color. Only what is embedded (code blocks, attachments, action cards) looks like a card.
- Put the task marker first in the footer, then the reactions, with the add button last.
- Make the task marker a status chip: the status pixel, the task number and the assignee ([Tasks](https://raft-design.botiverse.build/components/tasks.md)).
- Show one reaction chip per emoji. Put a reaction the person added on `attention-soft`.
- Keep a chip's ink as far from its left edge as from its right ([Alignment](https://raft-design.botiverse.build/foundations/alignment.md#chips)). A reference chip with an icon and a reaction split their padding evenly, 4.5px and 7px at each end, the same total and width as before: a Lucide icon leaves about as much space in its box as a letter beside it, and an emoji or an app's icon is drawn to its edges. The saved marker takes 5.5px before its bookmark, which is drawn 2.3px inside its box, and 6.5px after its text.
- Put a note after a reference chip's label in `MessageReferenceSuffix`, not in a span of your own. It stays on the label's baseline in the layout and is drawn a quarter of the size difference higher, so the middles of the two x-heights meet; the label stays on the sentence's baseline.
- Make a reaction chip 24px tall, with a 44px hit area ([Spacing](https://raft-design.botiverse.build/foundations/spacing.md#hit-targets)).

## API

### MessageReferenceChip

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `MessageReferenceChipVariant` | `"muted"` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReferenceIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReferenceLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReferencePending

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReferenceSuffix

A short note after a chip's label in muted machine type, such as `msg` on a link to a message. It lines up with the label by the middle of its letters.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReferenceText

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `MessageReferenceTextVariant` | `"secondary"` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReferenceUnavailable

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `compact` | `boolean` |  |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemAttachments

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemAvatarSlot

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemBadge

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `status` | `MessageItemBadgeStatus` | `"read"` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemBadgeSeparator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemBody

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemContinuationTime

Left-gutter hover clock for continuation rows, a `<time>`: pass the moment in `dateTime`. Show the hour and minute only, in the person's 12h/24h format, with no AM/PM (`6:35`, `18:35`).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemEdited

The edited marker, inline at the end of the message text on every row, full or continuation. Put it inside the last paragraph so it follows the last word; pass the copy ("(edited)") as children and the edit time as its tooltip.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemEmbedded

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemFooter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemGutter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemInlineStatus

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemReadReceipt

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemSavedIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `iconOnly` | `boolean` | `false` | Square icon-only affordance. Replaces the old `data-icon-only` attribute contract with an explicit prop. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemSearchMatch

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemSender

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemSenderBadges

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemSenderButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemSenderGroup

Holds the sender, its badges and its role, and decides who gives way when the header runs out of room: the role truncates first, then the name. The badges stay whole beside the name, and the time, outside the group, never truncates.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemTime

The message's time, right after the sender. A `<time>`: pass the moment in `dateTime` (ISO 8601) and the full date and time as its tooltip.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemToolbar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemToolbarButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageMultiSelect

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageMultiSelectCheckbox

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `shape` | `"circle" \| "square"` |  |  |
| `checked` | `boolean` | `undefined` | Whether the checkbox is currently ticked. |
| `defaultChecked` | `boolean` | `false` | Whether the checkbox is initially ticked. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `form` | `string` |  | Identifies the form that owns the hidden input. Useful when the checkbox is rendered outside the form. |
| `id` | `string` |  | The id of the input element. |
| `indeterminate` | `boolean` | `false` | Whether the checkbox is in a mixed state: neither ticked, nor unticked. |
| `inputRef` | `((instance: HTMLInputElement \| null) => void \| (() => void \| { [UNDEFINED_VOID_ONLY]: never; }) \| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) \| RefObject<HTMLInputElement \| null>` |  | A ref to access the hidden `<input>` element. |
| `name` | `string` | `undefined` | Identifies the field when a form is submitted. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onCheckedChange` | `(checked: boolean, eventDetails: CheckboxRootChangeEventDetails) => void` |  | Event handler called when the checkbox is ticked or unticked. |
| `parent` | `boolean` | `false` | Whether the checkbox controls a group of child checkboxes. |
| `readOnly` | `boolean` | `false` | Whether the user should be unable to tick or untick the checkbox. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `required` | `boolean` | `false` | Whether the user must tick the checkbox before submitting a form. |
| `uncheckedValue` | `string` |  | The value submitted with the form when the checkbox is unchecked. By default, unchecked checkboxes do not submit any value, matching native checkbox behavior. |
| `value` | `string` |  | The checkbox's value. Identifies it within a [Checkbox Group](https://base-ui.com/react/components/checkbox-group), falling back to `name` when omitted. When submitting a form, a checked box submits `value`; with no `value`, it submits the native "on". |

Also takes the attributes of its element.

### MessageMultiSelectToolbar

Shared select-mode toolbar shell. Consumers compose the count and actions as children.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageMultiSelectToolbarActions

The toolbar's actions, at its end, 4px apart like every floating toolbar's buttons.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionAddButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionChip

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultPressed` | `boolean` | `false` | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `onPressedChange` | `(pressed: boolean, eventDetails: BaseToggle.ChangeEventDetails) => void` |  | Callback fired when the pressed state is changed. |
| `pressed` | `boolean` |  | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `value` | `string` |  | A unique string that identifies the toggle when used inside a toggle group. |

Also takes the attributes of its element.

### MessageReactionCount

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionErrorIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionGlyph

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionPickerContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `portalProps` | `ContextMenuPortalProps` |  |  |
| `align` | `Align` | `'center'` | How to align the popup relative to the specified side. |
| `alignOffset` | `number \| OffsetFunction` | `0` | Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. |
| `anchor` | `Element \| VirtualElement \| RefObject<Element \| null> \| (() => Element \| VirtualElement \| null)` |  | An element to position the popup against. By default, the popup will be positioned against the trigger. |
| `arrowPadding` | `number` | `5` | Minimum distance to maintain between the arrow and the edges of the popup. |
| `children` | `ReactNode` |  |  |
| `collisionAvoidance` | `CollisionAvoidance` |  | Determines how to handle collisions when positioning the popup. |
| `collisionBoundary` | `Boundary` | `'clipping-ancestors'` | An element or a rectangle that delimits the area that the popup is confined to. |
| `collisionPadding` | `Padding` | `5` | Additional space to maintain from the edge of the collision boundary. |
| `disableAnchorTracking` | `boolean` | `false` | Whether to disable the popup from tracking any layout shift of its positioning anchor. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void)` |  | Determines the element to focus when the menu is closed. |
| `id` | `string` |  |  |
| `positionMethod` | `"fixed" \| "absolute"` | `'absolute'` | Determines which CSS `position` property to use. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `side` | `Side` | `'bottom'` | Which side of the anchor element to align the popup against. May automatically change to avoid collisions. |
| `sideOffset` | `number \| OffsetFunction` | `0` | Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. |
| `sticky` | `boolean` | `false` | Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. |

Also takes the attributes of its element.

### MessageReactionPickerStatic

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionQuickButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionQuickRow

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionTooltipContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `container` | `HTMLElement \| ShadowRoot \| RefObject<HTMLElement \| ShadowRoot \| null>` |  |  |
| `portalProps` | `TooltipPortalProps` |  |  |
| `align` | `Align` | `'center'` | How to align the popup relative to the specified side. |
| `alignOffset` | `number \| OffsetFunction` | `0` | Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. |
| `anchor` | `Element \| VirtualElement \| RefObject<Element \| null> \| (() => Element \| VirtualElement \| null)` |  | An element to position the popup against. By default, the popup will be positioned against the trigger. |
| `arrowPadding` | `number` | `5` | Minimum distance to maintain between the arrow and the edges of the popup. |
| `collisionAvoidance` | `CollisionAvoidance` |  | Determines how to handle collisions when positioning the popup. |
| `collisionBoundary` | `Boundary` | `'clipping-ancestors'` | An element or a rectangle that delimits the area that the popup is confined to. |
| `collisionPadding` | `Padding` | `5` | Additional space to maintain from the edge of the collision boundary. |
| `disableAnchorTracking` | `boolean` | `false` | Whether to disable the popup from tracking any layout shift of its positioning anchor. |
| `positionMethod` | `"fixed" \| "absolute"` | `'absolute'` | Determines which CSS `position` property to use. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `side` | `Side` | `'top'` | Which side of the anchor element to align the popup against. May automatically change to avoid collisions. |
| `sideOffset` | `number \| OffsetFunction` | `0` | Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. |
| `sticky` | `boolean` | `false` | Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. |

Also takes the attributes of its element.

### MessageReactionTooltipMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionTooltipName

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionTooltipNames

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionTooltipNameText

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageReactionTooltipSeparator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageTextSelectionToolbar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` |  |  |
| `loopFocus` | `boolean` | `true` | If `true`, using keyboard navigation will wrap focus to the other end of the toolbar once the end is reached. |
| `orientation` | `BaseToolbar.Orientation` | `'horizontal'` | The orientation of the toolbar. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageTextSelectionToolbarButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | When `true` the item is disabled. |
| `focusableWhenDisabled` | `boolean` | `true` | When `true` the item remains focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageTextSelectionToolbarSeparator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `BaseToolbar.Orientation` | `'horizontal'` | The orientation of the separator. Defaults to the opposite of the toolbar's orientation, so a horizontal toolbar renders vertical separators. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemDelegatedAvatar

The assistant's face on its owner's avatar, in the corner a presence light takes: put it inside the owner's `md` `Avatar` with a `2xs` agent `Avatar` as its child. It is hidden from assistive technology, because `MessageItemDelegatedBy` already says who sent the message. Only on a person's avatar: an agent's corner is its presence light.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `deleted` | `boolean` |  | The assistant was deleted: its face is grayed and faded, over the row's surface, never the owner's avatar. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemDelegatedBy

Says an assistant sent the message for its owner, the sender: "sent by Friday". Put it in `MessageItemHeader` after `MessageItemSenderGroup`, and the assistant's name in `MessageItemDelegatedByName` or `MessageItemDelegatedByButton`. The words, and the spaces around the name, come from the product. It takes at most 45% of the line: past that only the name truncates, never the words.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemDelegatedByButton

The assistant's name when it opens the assistant's profile: a `<button>` (or a link through `render`) that underlines on hover and truncates like `MessageItemDelegatedByName`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemDelegatedByName

The assistant's name in `MessageItemDelegatedBy`, the part that truncates.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.20.0 (Breaking Changes): **MessageMultiSelectToolbar**: 36px tall (was 48) with 28px buttons (was 32) 4px from the frame; on a touch screen each button takes a 44px tap. The buttons go in the new `MessageMultiSelectToolbarActions`.
- 0.12.0 (Added): A message an assistant sent for its owner, from `@botiverse/raft-ui/wip`: `MessageItemDelegatedBy`, `MessageItemDelegatedByName`, `MessageItemDelegatedByButton`, `MessageItemDelegatedAvatar` (with `deleted`) and `MessageQuotedPreviewAuthorDelegatedBy`.
- 0.12.0 (Fixed): `MessageItemSenderGroup`: the gap before a role squeezed to nothing collapses with it.
- 0.10.3 (Added): `MessageItemSenderGroup` wraps the name, badges and role and sets the order they give way in when the header is too narrow: the role truncates first, then the name; badges keep their width and the time never truncates. Existing headers without it keep working.
- 0.10.3 (Added): `MessageItemEdited`: an inline "(edited)" marker at the end of the message text, on every row.
- 0.10.3 (Changed): `MessageItemTime` sits right after the sender (name, badges, role) instead of at the far right, and no longer fades out under the hover toolbar: hover only adds, it never hides who said it or when. With a mouse the header keeps the toolbar's width free (`--mi-toolbar-reserve`, default 6.5rem for three buttons; set it on the list when the toolbar has more). This is visible wherever `MessageItemTime` is used, also without `MessageItemSenderGroup`; wrap the name, badges and role in it to get the give-way order.
- 0.10.3 (Changed): `MessageItemTime` and `MessageItemContinuationTime` render a `<time>` element and take `dateTime`.
- 0.10.3 (Fixed): A long name no longer pushes the time out of the header (it was cut to "14:3"): `MessageItemSender` / `MessageItemSenderButton` can shrink now.
