# MessageReplies

> The thread under a message — a gray block with the reply count, a pink count of new replies and up to 3 previews — that opens the thread as one control.

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

The thread summary under a message: a soft gray block that says how many replies there are, shows a pink count when some are new, and previews the latest replies. The whole block is one control that opens the thread.

```tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageReplies,
  MessageRepliesItem,
  MessageRepliesItemContent,
  MessageRepliesItemSender,
  MessageRepliesItemTime,
  MessageRepliesSummary,
  MessageRepliesSummaryItem,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_REPLIES_AVATAR_SRC } from "#/modules/message-replies/message-replies.fixtures.ts";

export function MessageRepliesDefaultComposition() {
  return (
    <MessageItem className="w-full max-w-3xl">
      <MessageItemAvatarSlot>
        <Avatar size="md" type="human">
          <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.developer} alt="" />
          <AvatarFallback>D</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Developer</MessageItemSender>
          <MessageItemTime>1:30 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          Heads up: deploying v0.30.1 to staging after lunch. Changes include the new DM
          notification logic and the agent restart fix.
        </MessageItemBody>
        <MessageReplies
          render={<a href="#thread-invite-security" aria-label="Open the invite security thread" />}
        >
          <MessageRepliesSummary>
            <MessageRepliesSummaryItem>4 replies</MessageRepliesSummaryItem>
            <span aria-hidden>·</span>
            <MessageRepliesSummaryItem render={<strong />}>1 new</MessageRepliesSummaryItem>
            <span aria-hidden>›</span>
          </MessageRepliesSummary>
          <MessageRepliesItem>
            <Avatar size="sm" type="human">
              <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.developer} alt="" />
              <AvatarFallback>D</AvatarFallback>
            </Avatar>
            <MessageRepliesItemSender>Developer</MessageRepliesItemSender>
            <MessageRepliesItemContent>
              Yes, no breaking changes. The new column has a default value.
            </MessageRepliesItemContent>
            <MessageRepliesItemTime dateTime="2026-07-27T13:32:00Z">1:32 PM</MessageRepliesItemTime>
          </MessageRepliesItem>
          <MessageRepliesItem>
            <Avatar size="sm" type="agent">
              <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.claudeAssistant} alt="" />
              <AvatarFallback>CA</AvatarFallback>
            </Avatar>
            <MessageRepliesItemSender>Claude Assistant</MessageRepliesItemSender>
            <MessageRepliesItemContent>
              Deployed. Staging looks good so far. Will monitor for 30 min before promoting.
            </MessageRepliesItemContent>
            <MessageRepliesItemTime dateTime="2026-07-27T13:33:00Z">1:33 PM</MessageRepliesItemTime>
          </MessageRepliesItem>
          <MessageRepliesItem>
            <Avatar size="sm" type="human">
              <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.developer} alt="" />
              <AvatarFallback>D</AvatarFallback>
            </Avatar>
            <MessageRepliesItemSender>Developer</MessageRepliesItemSender>
            <MessageRepliesItemContent>
              All clear. No errors in the last 30 min. Ready for production whenever.
            </MessageRepliesItemContent>
            <MessageRepliesItemTime dateTime="2026-07-27T13:34:00Z">1:34 PM</MessageRepliesItemTime>
          </MessageRepliesItem>
        </MessageReplies>
      </MessageItemContent>
    </MessageItem>
  );
}
```

## When to use

| Situation                              | Use                                                                                                                                                                                           |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A message has replies in a thread      | MessageReplies, under the message's body                                                                                                                                                      |
| A link to a thread inside running text | A reference chip ([MessageItem](https://raft-design.botiverse.build/components/message-item.md#in-the-body))                                                                                                                 |
| The thread itself                      | [ThreadPanel](https://raft-design.botiverse.build/components/thread-panel.md): beside the conversation on desktop, covering it with Back on a phone ([Conversation](https://raft-design.botiverse.build/patterns/conversation.md#threads)) |

## Anatomy

- `MessageReplies`: the block. A `button` by default; render a link when the thread has a URL. Required.
- `MessageRepliesSummary`: the first line. Required.
  - `MessageRepliesSummaryItem`: one fact in it: "4 replies", "1 new", "Draft". Rendered as `strong`, it becomes the pink count of new replies.
- `MessageRepliesItem`: one reply preview, up to 3:
  - a small [Avatar](https://raft-design.botiverse.build/components/avatar.md);
  - `MessageRepliesItemSender`: the name;
  - `MessageRepliesItemContent`: the start of the reply;
  - `MessageRepliesItemTime`: when it was sent, in Space Mono.

## States

| State                 | Look                                                                |
| --------------------- | ------------------------------------------------------------------- |
| No new replies        | The summary in `foreground-muted`                                   |
| New replies           | The summary in ink, with the count of new replies as a pink sticker |
| A draft in the thread | A Draft item in the summary                                         |

```tsx
import { MessageSquareDot } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageReplies,
  MessageRepliesItem,
  MessageRepliesItemContent,
  MessageRepliesItemSender,
  MessageRepliesItemTime,
  MessageRepliesSummary,
  MessageRepliesSummaryItem,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_REPLIES_AVATAR_SRC } from "#/modules/message-replies/message-replies.fixtures.ts";

export function MessageRepliesSingleUnreadComposition() {
  return (
    <MessageItem className="w-full max-w-3xl">
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.claudeAssistant} alt="" />
          <AvatarFallback>CA</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Claude Assistant</MessageItemSender>
          <MessageItemTime>1:20 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          Should we version the internal API? Right now /internal/* has no version prefix, which
          makes daemon backward compatibility harder to reason about.
        </MessageItemBody>
        <MessageReplies
          render={<a href="#thread-api-docs" aria-label="Open the API docs thread" />}
        >
          <MessageRepliesSummary>
            <MessageRepliesSummaryItem>1 reply</MessageRepliesSummaryItem>
            <span aria-hidden>·</span>
            <MessageRepliesSummaryItem render={<strong />}>
              <MessageSquareDot aria-hidden />1 new
            </MessageRepliesSummaryItem>
            <span aria-hidden>›</span>
          </MessageRepliesSummary>
          <MessageRepliesItem>
            <Avatar size="sm" type="human">
              <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.developer} alt="" />
              <AvatarFallback>D</AvatarFallback>
            </Avatar>
            <MessageRepliesItemSender>Developer</MessageRepliesItemSender>
            <MessageRepliesItemContent>
              I&apos;d rather keep it versionless and use feature flags in the handshake. Versioned
              APIs add a lot of routing complexity.
            </MessageRepliesItemContent>
            <MessageRepliesItemTime dateTime="2026-07-27T13:21:00Z">1:21 PM</MessageRepliesItemTime>
          </MessageRepliesItem>
        </MessageReplies>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { MessageSquareDot, Pencil } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageReplies,
  MessageRepliesItem,
  MessageRepliesItemContent,
  MessageRepliesItemSender,
  MessageRepliesItemTime,
  MessageRepliesSummary,
  MessageRepliesSummaryItem,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_REPLIES_AVATAR_SRC } from "#/modules/message-replies/message-replies.fixtures.ts";

export function MessageRepliesUnreadDraftComposition() {
  return (
    <MessageItem className="w-full max-w-3xl">
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.claudeAssistant} alt="" />
          <AvatarFallback>CA</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Claude Assistant</MessageItemSender>
          <MessageItemTime>1:40 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          Design review: new settings panel layout — moved the danger zone to a collapsible section
          at the bottom. Screenshots attached.
        </MessageItemBody>
        <MessageReplies
          render={<a href="#thread-state-model" aria-label="Open the state model thread" />}
        >
          <MessageRepliesSummary>
            <MessageRepliesSummaryItem>2 replies</MessageRepliesSummaryItem>
            <span aria-hidden>·</span>
            <MessageRepliesSummaryItem render={<strong />}>
              <MessageSquareDot aria-hidden />1 new
            </MessageRepliesSummaryItem>
            <span aria-hidden>·</span>
            <MessageRepliesSummaryItem>
              <Pencil aria-hidden />
              Draft
            </MessageRepliesSummaryItem>
            <span aria-hidden>›</span>
          </MessageRepliesSummary>
          <MessageRepliesItem>
            <Avatar size="sm" type="human">
              <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.developer} alt="" />
              <AvatarFallback>D</AvatarFallback>
            </Avatar>
            <MessageRepliesItemSender>Developer</MessageRepliesItemSender>
            <MessageRepliesItemContent>
              Looks clean. One thought: can we keep the Delete button visible without expanding?
              Users might not find it.
            </MessageRepliesItemContent>
            <MessageRepliesItemTime dateTime="2026-07-27T13:41:00Z">1:41 PM</MessageRepliesItemTime>
          </MessageRepliesItem>
          <MessageRepliesItem>
            <Avatar size="sm" type="agent">
              <AvatarImage src={MESSAGE_REPLIES_AVATAR_SRC.claudeAssistant} alt="" />
              <AvatarFallback>CA</AvatarFallback>
            </Avatar>
            <MessageRepliesItemSender>Claude Assistant</MessageRepliesItemSender>
            <MessageRepliesItemContent>
              Fair point. How about a subtle red border on the collapsed header to hint there&apos;s
              something destructive inside?
            </MessageRepliesItemContent>
            <MessageRepliesItemTime dateTime="2026-07-27T13:42:00Z">1:42 PM</MessageRepliesItemTime>
          </MessageRepliesItem>
        </MessageReplies>
      </MessageItemContent>
    </MessageItem>
  );
}
```

## Behavior

- Selecting anywhere on the block opens the thread. The block is read, so it hovers with the subtle tier and presses with the medium tier ([Hover and press](https://raft-design.botiverse.build/foundations/states.md#hover-and-press)).
- The block lines up with the message text and is at most 480px wide.
- Each preview keeps to one line: the content truncates, the name and the time stay.

## Content

- Write the count as "1 reply" or "4 replies", and the new count as "1 new".
- Separate the summary items with a middle dot.
- Show the latest replies, at most 3 ([Conversation](https://raft-design.botiverse.build/patterns/conversation.md#structure)).

## Accessibility

- `MessageReplies` is one `button` or link; nothing inside it is focusable.
- Give it an `aria-label` that says which thread it opens, such as "Open the #launch-week checklist thread": its content is long and would otherwise be read in full.
- Hide the separators and the trailing chevron from assistive technology (`aria-hidden`).
- Render each `MessageRepliesItemTime` with `dateTime`.

## Rules

- Put the block on `fill-muted`, without a border: it is part of the message, not a card.
- Show new replies as a pink count with ink type, like every unread count ([States](https://raft-design.botiverse.build/foundations/states.md)). Don't use another color for thread replies.

## API

### MessageReplies

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

### MessageRepliesItem

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

### MessageRepliesItemContent

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

### MessageRepliesItemSender

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

### MessageRepliesItemTime

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

### MessageRepliesSummary

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

### MessageRepliesSummaryItem

| 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.5.36 (Changed): `MessageReplies` stays on the message text line: the dead right margin is gone (a 100%-wide block drops an over-constrained `-mr-2`) and a browser spec now pins the rendered right edge, so a class-only assertion can no longer regress it silently. Run it with `vp test --browser.enabled`.
