cometchat-placement

SkillAI & models

Production integration patterns -- how to add CometChat as a route, modal, drawer, embedded panel, or widget in an existing project. Teaches Claude WHERE to put chat.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the cometchat-placement skill

What this skill tells your AI

The instructions your AI receives, as published by cometchat/cometchat-skills in skills/cometchat-placement/SKILL.md and read by ahel’s review.

Ground truth: @cometchat/chat-uikit-react@^6 components composed for placement + docs/ui-kit/react. Official docs: https://www.cometchat.com/docs/ui-kit/react/overview · Docs MCP: claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp (or fetch the URL directly without MCP). Verify symbols against the installed package/source before relying on them.

Purpose

This skill teaches you WHERE to put CometChat in an existing project. It covers five placement patterns: route, modal, drawer, embedded panel, and floating widget. Each pattern includes step-by-step instructions and complete code examples.

This skill is framework-AGNOSTIC. It uses generic instructions like "create a page at the framework's route location" and "add a route entry to the project's router." The framework-specific details (file paths, SSR handling, env var prefixes) come from the framework skill and the cometchat-core skill.

Before using this skill:

  • Read cometchat-core for initialization, login, CSS, and provider patterns
  • Read cometchat-components for component names, props, and composition patterns

"What are you building?" -- placement recommendation

Use this table to recommend a placement based on what the user is building. If the user says "add chat to my app" without specifying where, ask them what they are building and use this table.

User intentRecommended placementExperience composition
Messaging appRoute (full page)Multi-conversation (CometChatConversations + MessageHeader + MessageList + MessageComposer)
Marketplace / platformDrawer on product page + /messages routeSingle thread (drawer) + Multi-conversation (route)
SaaS / dashboardModal from navbar + /messages routeSingle thread (modal) + Multi-conversation (route)
Social / communityRoute (tabs)Full messenger (CometChatConversations + CallLogs + Users + Groups with tabs)
Support / helpdeskFloating widgetWidget (use CLI)
Just exploringDemo (replace home page)Multi-conversation

Visual reference — experience layouts

When presenting experience options to the user, describe these layouts or share the ASCII art so they can visualize what each looks like.

Multi-conversation (Experience 1)

Two-pane layout: conversation list on the left, active chat thread on the right.

┌─────────────────────────┬───────────────────────────────────────┐
│ Chats               Q   │ Richard Ray               v  c  i     │
├─────────────────────────┼───────────────────────────────────────┤
│                         │                                       │
│ (RR) Richard Ray  3:45  │           ╭─────────────────────╮     │
│      Is it still up..   │           │ Hi, is the watch    │     │
│                         │           │ still up for sale?  │     │
│ (SB) Sarah Beth   3:40  │           ╰────────── 4:56 PM ─╯      │
│      Sure! Sending ..   │                                       │
│                         │ ╭─────────────────╮                   │
│ (RA) Robert Allen 3:38  │ │ Yes, it is      │                   │
│      Thanks! Looks ..   │ │ available.      │                   │
│                         │ ╰─ 4:56 PM ──────╯                    │
│ (SG) Sam Game     3:30  │                                       │
│      Sending them ..    │           ╭─────────────────────╮     │
│                         │           │ Can I see a couple  │     │
│ (SF) Scott F.     3:22  │           │ of pictures?        │     │
│      I will look ..     │           ╰────────── 4:56 PM ─╯      │
│                         │                                       │
│ (EP) Evan Parker  3:15  │ ╭─────────────────╮                   │
│      Hey, did you ..    │ │ Sure! Sending   │                   │
│                         │ │ them over now.  │                   │
│ (JP) John Paul    3:10  │ ╰─ 4:56 PM ──────╯                    │
│      Sounds good        │                                       │
│                         │           ╭─────────────────────╮     │
│ (LK) Linda Kay    3:05  │           │ Thanks! Looks good. │     │
│      See you there      │           ╰────────── 4:56 PM ─╯      │
│                         ├───────────────────────────────────────┤
│                         │ Type a message...                 >   │
└─────────────────────────┴───────────────────────────────────────┘

Best for: messaging apps, team chat, inboxes, dedicated chat sections.

Single thread (Experience 2)

One chat window — no conversation list. Shows a direct chat with one user or group.

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│ (RR) Richard Ray                              v    c    i       │
│      . Online                                                   │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                        ╭───────────────────────────────╮        │
│                        │ Hi, is the watch still up     │        │
│                        │ for sale?          4:56 PM vv │        │
│                        ╰───────────────────────────────╯        │
│                                                                 │
│ ╭───────────────────────╮                                       │
│ │ Yes, it is available. │                                       │
│ ╰── 4:56 PM ───────────╯                                        │
│                                                                 │
│                        ╭───────────────────────────────╮        │
│                        │ Awesome! Can I see a couple   │        │
│                        │ of pictures?       4:56 PM vv │        │
│                        ╰───────────────────────────────╯        │
│                                                                 │
│ ╭────────────────────────────────╮                              │
│ │ Sure! Sending them over now.   │                              │
│ ╰── 4:56 PM ────────────────────╯                               │
│                                                                 │
│                        ╭───────────────────────────────╮        │
│                        │ Thanks! Looks good. 4:56 PM vv│        │
│                        ╰───────────────────────────────╯        │
│                                                                 │
│ ╭─────────────╮                                                 │
│ │ Thank you!  │                                                 │
│ ╰── 4:56 PM ─╯                                                  │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│ +  m  e  a     Type a message...                            >   │
└─────────────────────────────────────────────────────────────────┘

Best for: marketplace chat, embedded consult, support, one-on-one conversations.

Full messenger (Experience 3)

Two-pane layout like Experience 1, plus a bottom tab bar for switching between Chats, Calls, Users, and Groups.

┌─────────────────────────┬───────────────────────────────────────┐
│ Chats               Q   │ Richard Ray               v  c  i     │
├─────────────────────────┼───────────────────────────────────────┤
│                         │                                       │
│ (RR) Richard Ray  3:45  │           ╭─────────────────────╮     │
│      How much extra..   │           │ How much extra are  │     │
│                         │           │ we talking for the  │     │
│ (SB) Sarah Beth   3:40  │           │ direct flight?      │     │
│      That sounds w..    │           ╰────────── 4:56 PM ─╯      │
│                         │                                       │
│ (RA) Robert Allen 3:38  │ ╭──────────────────────╮              │
│      4:56 PM            │ │ It is $50 more. Save │              │
│                         │ │ a couple of hours.   │              │
│ (SG) Sam Game     3:30  │ ╰─ 4:56 PM ───────────╯               │
│      Sending them ..    │                                       │
│                         │           ╭─────────────────────╮     │
│ (SF) Scott F.     3:22  │           │ That sounds worth   │     │
│      I will look ..     │           │ it. Let us do it.   │     │
│                         │           ╰────────── 4:56 PM ─╯      │
│ (EP) Evan Parker  3:15  │                                       │
│      Hey, did you ..    │ ╭──────────────────────╮              │
│                         │ │ Great, I will send   │              │
│                         │ │ you the details.     │              │
│                         │ ╰─ 4:56 PM ───────────╯               │
│                         ├───────────────────────────────────────┤
│                         │ Type a message...                 >   │
├─────────────────────────┼───────────────────────────────────────┤
│ Ch   Ca   Us   Gr       │                                       │
└─────────────────────────┴───────────────────────────────────────┘

Best for: social apps, community platforms, dating apps, full-featured chat products.


Composer choice for narrow/two-pane panes: the canonical React v6 sample apps use CometChatCompactMessageComposer (not CometChatMessageComposer) in the two-pane messages view — it's the kit's space-optimized composer for a sidebar-width pane (cometchat-uikit-react-v6/sample-app/src/components/CometChatMessages/CometChatMessages.tsx:85). Both compile and work; prefer CometChatCompactMessageComposer for the split/two-pane layouts below and CometChatMessageComposer for full-width single-thread layouts.

Height & scroll — the message-view column MUST be bounded

Every header+list+composer example below puts the three components as direct flex children of a display:flex; flexDirection:column column. That works ONLY if the column has a bounded height — i.e. the full chain (html/body/#root → …→ this column) uses a definite height (e.g. #root { height: 100vh }), never min-height, and every flex ancestor of CometChatMessageList has minHeight: 0. Delete the Vite/CRA starter #root { min-height: 100vh; max-width: … }.

If the list still won't scroll (most common symptom), or you wrap the list in its own <div> for custom chrome/tabs, you hit the kit's auto-injected .cometchat element — see cometchat-react-patterns → "Container height (and the flex-shrink trap)" Bug 3: give the list its own wrapper class with BOTH .your-list-wrapper { flex: 1 1 0; min-height: 0; height: 100%; overflow: hidden } (the explicit height: 100% is required — a flex-grow-only height is indefinite, so the kit's injected child collapses to content height and clips) AND a child rule .your-list-wrapper > .cometchat { height: 100%; overflow: hidden } (inline styles can't target the injected child). Those two rules are the single most common fix for "I set heights everywhere and it still won't scroll."


Thread replies — hidden by default in every example below

Every <CometChatMessageList ...> in the placement patterns below includes hideReplyInThreadOption. The kit's default (false) puts a "Reply in Thread" entry in every message's action menu — but that entry only works if the integrator has wired up a thread panel (CometChatThreadHeader + a scoped CometChatMessageList + CometChatMessageComposer with parentMessageId). If the thread panel isn't wired (the case for a simple drawer, widget, modal, or single-thread experience), the option is still visible and clicking it silently does nothing — confusing UX.

Default: threads hidden. To enable threads for an experience that actually has the side-panel plumbing:

  1. Remove hideReplyInThreadOption from the main <CometChatMessageList>
  2. Add onThreadRepliesClick to capture the thread message
  3. Render the thread panel (see cometchat-components § Threading for the full pattern — CometChatThreadHeader + scoped MessageList + scoped MessageComposer with parentMessageId)

Route placement

The most common pattern. Chat gets its own page in the app, accessible via navigation.

Steps

1. Set up CometChatProvider at the app root

The provider (from cometchat-core) should wrap the entire app or the chat route's layout. This ensures init and login happen once, not per-navigation.

  • Read the project's existing layout/root component first. Look for the outermost wrapper (e.g., App.tsx, layout.tsx, root.tsx).
  • Add the CometChatProvider inside the existing layout, wrapping the router outlet or children.
  • Import @cometchat/chat-uikit-react/css-variables.css at the app root CSS file if not already imported.
2. Create a chat page component

Create a new file (e.g., ChatPage.tsx or MessagesPage.tsx) at the framework's conventional page location:

  • React (Vite): src/pages/ChatPage.tsx or src/ChatPage.tsx
  • Next.js (App Router): app/chat/page.tsx
  • Next.js (Pages Router): pages/chat.tsx
  • Astro: src/pages/chat.astro (with a React island)
  • React Router: app/routes/chat.tsx
3. Implement the page

Choose the experience composition from cometchat-components:

Two-pane (most common for routes):

// ChatPage.tsx
import { useState } from "react";
import {
  CometChatConversations,
  CometChatMessageHeader,
  CometChatMessageList,
  CometChatMessageComposer,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";

export default function ChatPage() {
  const [selectedUser, setSelectedUser] = useState<CometChat.User>();
  const [selectedGroup, setSelectedGroup] = useState<CometChat.Group>();

  function handleConversationClick(conversation: CometChat.Conversation) {
    const entity = conversation.getConversationWith();
    if (entity instanceof CometChat.User) {
      setSelectedUser(entity);
      setSelectedGroup(undefined);
    } else if (entity instanceof CometChat.Group) {
      setSelectedUser(undefined);
      setSelectedGroup(entity);
    }
  }

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      <div style={{ width: "360px", borderRight: "1px solid #eee" }}>
        <CometChatConversations onItemClick={handleConversationClick} />
      </div>
      <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
        {(selectedUser || selectedGroup) ? (
          <>
            {selectedUser && <CometChatMessageHeader user={selectedUser} />}
            {selectedGroup && <CometChatMessageHeader group={selectedGroup} />}
            {selectedUser && <CometChatMessageList user={selectedUser} hideReplyInThreadOption />}
            {selectedGroup && <CometChatMessageList group={selectedGroup} hideReplyInThreadOption />}
            {selectedUser && <CometChatMessageComposer user={selectedUser} />}
            {selectedGroup && <CometChatMessageComposer group={selectedGroup} />}
          </>
        ) : (
          <div style={{
            flex: 1,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            color: "#999",
          }}>
            Select a conversation to start chatting
          </div>
        )}
      </div>
    </div>
  );
}

Full messenger (tabs -- for standalone messaging sections):

// MessagesPage.tsx
import { useState } from "react";
import {
  CometChatConversations,
  CometChatCallLogs,
  CometChatUsers,
  CometChatGroups,
  CometChatMessageHeader,
  CometChatMessageList,
  CometChatMessageComposer,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";

type Tab = "chats" | "calls" | "users" | "groups";

export default function MessagesPage() {
  const [activeTab, setActiveTab] = useState<Tab>("chats");
  const [selectedUser, setSelectedUser] = useState<CometChat.User>();
  const [selectedGroup, setSelectedGroup] = useState<CometChat.Group>();

  function selectUser(user: CometChat.User) {
    setSelectedUser(user);
    setSelectedGroup(undefined);
  }
  function selectGroup(group: CometChat.Group) {
    setSelectedUser(undefined);
    setSelectedGroup(group);
  }

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      <div style={{ width: "360px", display: "flex", flexDirection: "column", borderRight: "1px solid #eee" }}>
        <nav style={{ display: "flex", borderBottom: "1px solid #eee" }}>
          {(["chats", "calls", "users", "groups"] as Tab[]).map((tab) => (
            <button
              key={tab}
              onClick={() => setActiveTab(tab)}
              style={{
                flex: 1,
                padding: "12px 0",
                border: "none",
                background: "none",
                cursor: "pointer",
                fontWeight: activeTab === tab ? 700 : 400,
                borderBottom: activeTab === tab ? "2px solid var(--cometchat-primary-color, #3399ff)" : "2px solid transparent",
              }}
            >
              {tab.charAt(0).toUpperCase() + tab.slice(1)}
            </button>
          ))}
        </nav>
        <div style={{ flex: 1, overflow: "hidden" }}>
          {activeTab === "chats" && (
            <CometChatConversations
              onItemClick={(conv) => {
                const entity = conv.getConversationWith();
                if (entity instanceof CometChat.User) selectUser(entity);
                else if (entity instanceof CometChat.Group) selectGroup(entity);
              }}
            />
          )}
          {activeTab === "calls" && <CometChatCallLogs />}
          {activeTab === "users" && <CometChatUsers onItemClick={selectUser} />}
          {activeTab === "groups" && <CometChatGroups onItemClick={selectGroup} />}
        </div>
      </div>
      <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
        {selectedUser && (
          <>
            <CometChatMessageHeader user={selectedUser} />
            <CometChatMessageList user={selectedUser} hideReplyInThreadOption />
            <CometChatMessageComposer user={selectedUser} />
          </>
        )}
        {selectedGroup && (
          <>
            <CometChatMessageHeader group={selectedGroup} />
            <CometChatMessageList group={selectedGroup} hideReplyInThreadOption />
            <CometChatMessageComposer group={selectedGroup} />
          </>
        )}
        {!selectedUser && !selectedGroup && (
          <div style={{
            flex: 1,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            color: "#999",
          }}>
            Select a conversation to start chatting
          </div>
        )}
      </div>
    </div>
  );
}
4. Wire the route into the project's router

Read the project's existing routing setup first. Do not assume a pattern. Look for:

  • React Router: createBrowserRouter(), <Routes>, <Route> in App.tsx or a routes file
  • Next.js App Router: app/ directory -- just creating the page file at app/chat/page.tsx IS the route
  • Next.js Pages Router: pages/ directory -- creating pages/chat.tsx IS the route
  • Astro: src/pages/ directory -- creating src/pages/chat.astro IS the route
  • React Router v7: File-based routing in app/routes/ or manual routes in app/routes.ts

For manual routers (React Router), add a route entry:

// Example: adding to an existing createBrowserRouter
{
  path: "/chat",
  element: <ChatPage />,
}

For file-based routers (Next.js, Astro, React Router v7), creating the file at the right path is sufficient.

5. Add a navigation link

Read the project's existing navbar/sidebar first. Find the component that renders navigation links (could be Navbar.tsx, Sidebar.tsx, Header.tsx, Nav.tsx, or inline in a layout).

Add a "Messages" or "Chat" link alongside the existing links:

// Example: adding to an existing nav component
<Link to="/chat">Messages</Link>
// or
<a href="/chat">Messages</a>

Match the existing link style. If the nav uses icons, add a chat/message icon. If it uses a specific NavLink or Link component, use the same one.

6. Import CSS

Check if @cometchat/chat-uikit-react/css-variables.css is already imported at the app root. If not, add it to the root CSS file or root layout:

/* In globals.css or index.css at the app root */
@import "@cometchat/chat-uikit-react/css-variables.css";

Modal placement

A centered overlay for quick one-off messages. Use when chat is a secondary action (e.g., "message this user" from a profile page).

When to use modal vs. drawer

  • Modal: Quick, one-off messages. User sends a message and closes. No ongoing conversation visible.
  • Drawer: Ongoing conversation. User keeps the drawer open while browsing the main app. Better for marketplace/support contexts.

Steps

1. Create a ChatModal component
// ChatModal.tsx
import { useEffect, useState } from "react";
import {
  CometChatMessageHeader,
  CometChatMessageList,
  CometChatMessageComposer,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";

interface ChatModalProps {
  isOpen: boolean;
  onClose: () => void;
  targetUserId?: string;
  targetGroupId?: string;
}

export function ChatModal({ isOpen, onClose, targetUserId, targetGroupId }: ChatModalProps) {
  const [user, setUser] = useState<CometChat.User>();
  const [group, setGroup] = useState<CometChat.Group>();
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!isOpen) return;
    setLoading(true);

    if (targetUserId) {
      CometChat.getUser(targetUserId)
        .then((u) => {
          setUser(u);
          setGroup(undefined);
          setLoading(false);
        })
        .catch(() => setLoading(false));
    } else if (targetGroupId) {
      CometChat.getGroup(targetGroupId)
        .then((g) => {
          setUser(undefined);
          setGroup(g);
          setLoading(false);
        })
        .catch(() => setLoading(false));
    }
  }, [isOpen, targetUserId, targetGroupId]);

  if (!isOpen) return null;

  return (
    <div
      style={{
        position: "fixed",
        inset: 0,
        zIndex: 1000,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      {/* Backdrop */}
      <div
        onClick={onClose}
        style={{
          position: "absolute",
          inset: 0,
          backgroundColor: "rgba(0, 0, 0, 0.5)",
        }}
      />

      {/* Modal content */}
      <div
        style={{
          position: "relative",
          width: "min(600px, 90vw)",
          height: "min(700px, 80vh)",
          backgroundColor: "var(--cometchat-background-color-01, #fff)",
          borderRadius: "var(--cometchat-border-radius-lg, 12px)",
          overflow: "hidden",
          display: "flex",
          flexDirection: "column",
          boxShadow: "0 20px 60px rgba(0, 0, 0, 0.3)",
        }}
      >
        {/* Close button */}
        <button
          onClick={onClose}
          style={{
            position: "absolute",
            top: 8,
            right: 8,
            zIndex: 10,
            background: "none",
            border: "none",
            fontSize: 20,
            cursor: "pointer",
            padding: "4px 8px",
          }}
          aria-label="Close chat"
        >
          X
        </button>

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
105
Forks
2
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
cometchat-placement
Source
github.com/cometchat/cometchat-skills