> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-chore-codeowners-swapnil-to-jitvar.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Threaded Messages

> Add threaded message replies to your chat so users can create focused sub-conversations on any message.

## Goal

By the end of this guide you will have a chat interface where users can open a thread panel from any message, view the parent message with its reply count, browse threaded replies, and send new replies — all using v7 compound components and a state-based approach (no custom events needed).

## Prerequisites

* Completed the [Integration Guide](/ui-kit/react/integration-react) guide
* A running `CometChatProvider` setup with valid credentials
* An existing chat screen using `CometChatMessageList` and `CometChatMessageComposer`

## Components Used

| Component                  | Purpose                                                                                              |
| :------------------------- | :--------------------------------------------------------------------------------------------------- |
| `CometChatThreadHeader`    | Displays the parent message and reply count at the top of the thread panel                           |
| `CometChatMessageList`     | Renders threaded replies when given a `parentMessage` (the deprecated `parentMessageId` still works) |
| `CometChatMessageComposer` | Sends replies into the thread with `parentMessageId`, `layout="compact"`, and `enableRichTextEditor` |

## Step 1: Thread State Management

Store the `threadedMessage` in state. When set, the thread panel renders. When cleared, it closes. This mirrors the pattern used in the sample app's `CometChatThreadPanel` component.

*File: ChatWithThreads.tsx*

```tsx theme={null}
import { useState } from "react";
import { CometChat } from "@cometchat/chat-sdk-javascript";

function ChatWithThreads() {
  const [user, setUser] = useState<CometChat.User | null>(null);
  const [group, setGroup] = useState<CometChat.Group | null>(null);
  const [threadedMessage, setThreadedMessage] = useState<CometChat.BaseMessage | null>(null);

  // Thread opens when threadedMessage is set, closes when cleared
}
```

## Step 2: Wire the Thread Trigger

Use the `onThreadRepliesClick` callback on `CometChatMessageList` to capture when a user clicks "Reply in Thread." This sets the threaded message and opens the panel — no events required.

*File: ChatWithThreads.tsx*

```tsx theme={null}
import { CometChatMessageList } from "@cometchat/chat-uikit-react";

<CometChatMessageList
  user={user ?? undefined}
  group={group ?? undefined}
  onThreadRepliesClick={(message) => setThreadedMessage(message)}
/>
```

## Step 3: Build the Thread Panel

When `threadedMessage` is set, render a side panel composing `CometChatThreadHeader` + `CometChatMessageList` (with `parentMessage`) + `CometChatMessageComposer` (with `parentMessageId`, `layout="compact"`, and `enableRichTextEditor`). The `onClose` callback clears the state to dismiss the panel.

*File: ChatWithThreads.tsx*

```tsx theme={null}
import {
  CometChatThreadHeader,
  CometChatMessageList,
  CometChatMessageComposer,
} from "@cometchat/chat-uikit-react";

{threadedMessage && (
  <div style={{ width: "400px", borderLeft: "1px solid #e0e0e0", display: "flex", flexDirection: "column" }}>
    <CometChatThreadHeader
      parentMessage={threadedMessage}
      onClose={() => setThreadedMessage(null)}
      onParentDeleted={() => setThreadedMessage(null)}
    />

    <div style={{ flex: 1, overflow: "hidden" }}>
      <CometChatMessageList
        parentMessage={threadedMessage}
        user={user ?? undefined}
        group={group ?? undefined}
      />
    </div>

    <CometChatMessageComposer
      parentMessageId={threadedMessage.getId()}
      user={user ?? undefined}
      group={group ?? undefined}
      layout="compact"
      enableRichTextEditor
    />
  </div>
)}
```

## Step 4: Handle Parent Deleted

Use the `onParentDeleted` prop on `CometChatThreadHeader` to automatically close the thread panel when the parent message is deleted by another user or a moderation action.

*File: ChatWithThreads.tsx*

```tsx theme={null}
<CometChatThreadHeader
  parentMessage={threadedMessage}
  onClose={() => setThreadedMessage(null)}
  onParentDeleted={() => setThreadedMessage(null)}
/>
```

## Complete Example

*File: App.tsx*

```tsx theme={null}
import { useState } from "react";
import { CometChat } from "@cometchat/chat-sdk-javascript";
import {
  CometChatProvider,
  CometChatConversations,
  CometChatMessageList,
  CometChatMessageComposer,
  CometChatMessageHeader,
  CometChatThreadHeader,
} from "@cometchat/chat-uikit-react";

function ChatWithThreads() {
  const [user, setUser] = useState<CometChat.User | null>(null);
  const [group, setGroup] = useState<CometChat.Group | null>(null);
  const [threadedMessage, setThreadedMessage] = useState<CometChat.BaseMessage | null>(null);

  function handleConversationClick(conversation: CometChat.Conversation) {
    setThreadedMessage(null);
    const entity = conversation.getConversationWith();
    if (entity instanceof CometChat.User) {
      setUser(entity);
      setGroup(null);
    } else if (entity instanceof CometChat.Group) {
      setGroup(entity);
      setUser(null);
    }
  }

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      {/* Conversations sidebar */}
      <div style={{ width: "300px", borderRight: "1px solid #e0e0e0" }}>
        <CometChatConversations onItemClick={handleConversationClick} />
      </div>

      {/* Main message panel */}
      <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
        {(user || group) && (
          <>
            <CometChatMessageHeader user={user ?? undefined} group={group ?? undefined} />
            <div style={{ flex: 1, overflow: "hidden" }}>
              <CometChatMessageList
                user={user ?? undefined}
                group={group ?? undefined}
                onThreadRepliesClick={(message) => setThreadedMessage(message)}
              />
            </div>
            <CometChatMessageComposer user={user ?? undefined} group={group ?? undefined} />
          </>
        )}
      </div>

      {/* Thread panel */}
      {threadedMessage && (
        <div style={{ width: "400px", borderLeft: "1px solid #e0e0e0", display: "flex", flexDirection: "column" }}>
          <CometChatThreadHeader
            parentMessage={threadedMessage}
            onClose={() => setThreadedMessage(null)}
            onParentDeleted={() => setThreadedMessage(null)}
          />
          <div style={{ flex: 1, overflow: "hidden" }}>
            <CometChatMessageList
              parentMessage={threadedMessage}
              user={user ?? undefined}
              group={group ?? undefined}
            />
          </div>
          <CometChatMessageComposer
            parentMessageId={threadedMessage.getId()}
            user={user ?? undefined}
            group={group ?? undefined}
            layout="compact"
            enableRichTextEditor
          />
        </div>
      )}
    </div>
  );
}

function App() {
  return (
    <CometChatProvider>
      <ChatWithThreads />
    </CometChatProvider>
  );
}

export default App;
```

## Thread Subscription

Users can *subscribe* to a thread to keep getting updates about new replies even when they aren't actively viewing it, and *unsubscribe* to stop. This works in both 1:1 and group conversations. The UI Kit surfaces this in two places, both wired out of the box:

* A **bell toggle** in `CometChatThreadHeader`.
* A **subscribe / unsubscribe option** in the message context menu of `CometChatMessageList`.

Both reflect the current subscription state, flip it optimistically on click, and show a toast if the server rejects the change. No wiring is required to make them work.

### Automatic subscription

Beyond the manual bell and menu option, the UI Kit subscribes a user to a thread automatically in a few cases, so people keep getting updates on threads they're actually part of — without having to remember to follow them:

* **Sending a message subscribes you to its thread.** When you send a message, you're subscribed to the thread on that message — so you keep hearing about replies to it — and sending a reply inside a thread subscribes you to that thread as well. This applies to every message type — text, media, stickers, polls, collaborative documents, and custom messages.
* **Being @mentioned in a reply subscribes you.** If someone @mentions you in a threaded reply, you're subscribed — whether the mention is added on a fresh reply or introduced (or preserved) by an edit.
* **A reply from someone else that doesn't mention you does not subscribe you.** You're only pulled in when you author something in the thread or you're mentioned.

These rules apply in real time and across devices, and a change is mirrored to every surface — the bell in `CometChatThreadHeader` and the subscribe/unsubscribe option in `CometChatMessageList` — **whether or not the thread panel is open**.

A deliberate **unsubscribe is remembered** and survives a reload, but sending another message in that thread, or being mentioned in it again, re-subscribes you.

The labels and toasts are localizable — override the `thread_subscription_subscribe`, `thread_subscription_unsubscribe`, `thread_subscription_subscribed_toast`, `thread_subscription_unsubscribed_toast`, and `thread_subscription_failed` keys via [localization](/ui-kit/react/localization).

### Reacting to changes

Pass `onThreadSubscriptionChange` to the thread header to run your own logic when the user subscribes or unsubscribes:

```tsx theme={null}
<CometChatThreadHeader
  parentMessage={threadedMessage}
  onClose={() => setThreadedMessage(null)}
  onThreadSubscriptionChange={(subscribed) =>
    console.log(subscribed ? "Subscribed to thread" : "Unsubscribed from thread")
  }
/>
```

### Hiding the controls

* `hideThreadSubscriptionToggle` on [`CometChatThreadHeader`](/ui-kit/react/components/thread-header#hidethreadsubscriptiontoggle) removes the bell.
* `hideThreadSubscriptionOption` on [`CometChatMessageList`](/ui-kit/react/components/message-list#hidethreadsubscriptionoption) removes the context-menu option.

### Driving it yourself

For custom UI, the `useThreadSubscription` hook exposes the same state and toggle. Pass an optional second argument to react when the subscription flips (from the toggle here, or from any automatic or manual change elsewhere):

```tsx theme={null}
import { useThreadSubscription } from "@cometchat/chat-uikit-react";

function SubscribeButton({ parentMessage }: { parentMessage: CometChat.BaseMessage }) {
  const { isSubscribed, toggle } = useThreadSubscription(parentMessage, (subscribed) =>
    console.log(subscribed ? "Subscribed" : "Unsubscribed")
  );
  return (
    <button onClick={toggle}>
      {isSubscribed ? "Subscribed" : "Subscribe to thread"}
    </button>
  );
}
```

If you only need to read the state — for example, to render an indicator without a toggle — use `useThreadSubscriptionState`, which returns the boolean alone and stays in sync with every automatic and manual change:

```tsx theme={null}
import { useThreadSubscriptionState } from "@cometchat/chat-uikit-react";

function SubscribedDot({ parentMessage }: { parentMessage: CometChat.BaseMessage }) {
  const isSubscribed = useThreadSubscriptionState(parentMessage);
  return isSubscribed ? <span className="subscribed-dot" /> : null;
}
```

Subscription changes are broadcast on the [event bus](/ui-kit/react/event-system) as `ui:thread/subscription-changed` — the single channel every open surface (the bell, the option, your custom UI) listens to, so a flip anywhere is reflected everywhere.

## Next Steps

* [Thread Header](/ui-kit/react/components/thread-header) — customize the thread header appearance
* [Message List](/ui-kit/react/components/message-list) — configure message list rendering and options
* [CometChatProvider](/ui-kit/react/cometchat-provider) — learn about provider configuration
