Skip to content
OpenSmartRoute
Skillv1.0.0

cometchat-placement

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.

by cometchat(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from cometchat/cometchat-skills (skills/cometchat-placement/SKILL.md). Install upstream with npx skills add cometchat/cometchat-skills --skill cometchat-placement. Copyright stays with the author (MIT).

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 intent Recommended placement Experience composition
Messaging app Route (full page) Multi-conversation (CometChatConversations + MessageHeader + MessageList + MessageComposer)
Marketplace / platform Drawer on product page + /messages route Single thread (drawer) + Multi-conversation (route)
SaaS / dashboard Modal from navbar + /messages route Single thread (modal) + Multi-conversation (route)
Social / community Route (tabs) Full messenger (CometChatConversations + CallLogs + Users + Groups with tabs)
Support / helpdesk Floating widget Widget (use CLI)
Just exploring Demo (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>

        {loading ? (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
            Loading...
          </div>
        ) : (
          <>
            {user && <CometChatMessageHeader user={user} />}
            {group && <CometChatMessageHeader group={group} />}
            <div style={{ flex: 1, overflow: "hidden" }}>
              {user && <CometChatMessageList user={user} hideReplyInThreadOption />}
              {group && <CometChatMessageList group={group} hideReplyInThreadOption />}
            </div>
            {user && <CometChatMessageComposer user={user} />}
            {group && <CometChatMessageComposer group={group} />}
          </>
        )}
      </div>
    </div>
  );
}

2. Wire the trigger

Read the project's components to find the right trigger point. This could be:

  • A "Message" button on a user profile page
  • A "Contact Seller" button on a product card
  • A chat icon in a navbar
  • A "Send Message" link in a user list
// Example: adding a chat button to an existing product card
import { useState } from "react";
import { ChatModal } from "./ChatModal";

function ProductCard({ product }) {
  const [showChat, setShowChat] = useState(false);

  return (
    <div>
      {/* existing product card content */}
      <button onClick={() => setShowChat(true)}>
        Message Seller
      </button>
      <ChatModal
        isOpen={showChat}
        onClose={() => setShowChat(false)}
        targetUserId={product.sellerId}
      />
    </div>
  );
}

3. CometChatProvider placement

The CometChatProvider (or equivalent init logic) MUST be at the app root, NOT inside the modal. If init is inside the modal, it re-runs every time the modal opens, causing flicker and reconnection delays.


Drawer placement

A side panel that slides in from the right. Better than a modal for ongoing conversations because the user can keep it open while browsing.

⚠️ Known kit-side traps in narrow containers (≤ ~480px). Mitigate, don't ignore (ENG-35702/03/04/06):

  1. Unread-count badges clip at the right edge in 320px panes — kit's conversation-list item doesn't reserve trailing padding for the pill. Workaround until the kit fix lands: target .cometchat-conversation-list-item__badge { margin-right: 8px; flex-shrink: 0; } in your overrides, OR widen the drawer to ≥ 480px on the inner content (use width: min(420px, 100vw) + 8px inner padding).
  2. Delete Conversation? confirmation card overflows the list pane — kit renders the dialog inside the list column instead of portalling to a top-level scrim. There's no clean CSS workaround today; if the integration depends on right-click delete, raise the drawer width above ~480px so the card fits inside.
  3. Composer attachment popover (+ button) anchors to the wrong ancestor — opens at viewport bottom-right instead of above the trigger when mounted inside a drawer/modal. Workaround: ensure the drawer container has position: relative (NOT just position: fixed) on its inner card so absolute popovers anchor correctly. If the popover still misaligns, mount the composer in a full-screen-on-mobile variant (the right: -100% recipe from §"Recipe — breakpoint-aware drawer").
  4. Voice recorder bubble UI is broken at kit v6.x — the in-bubble recorder shows visual artifacts AND the pause-resume timer restarts at 00:00 instead of continuing. There is no client-side workaround; if voice messages are part of the integration's core surface, document the limitation for the customer and consider hiding the voice button (<CometChatMessageComposer hideVoiceRecordingButton={true} />) until the kit fix ships.
  5. CometChatMessageList does NOT auto-resubscribe on conversation switch — passing a new user or group prop keeps the old listeners. Required workaround when switching conversations inside a single drawer/modal: pass key={user?.getUid() ?? group?.getGuid()} to force a remount + listener re-subscribe. Without it, switching from Alice → Bob silently shows Alice's messages with Bob's header. (ENG-35702)
  6. CometChatMessageList may render BLANK on conversations with unread messages — kit-side data-path bug, no header or empty-state, just blank. No client-side workaround today; if the customer reports a blank list, capture the screenshot + UID/GUID + unread count and file a kit ticket. (ENG-35702 kit-side blocker)

These are kit-side bugs filed in Linear (ENG-35702, 35703, 35704, 35706); the workarounds above are interim until the next kit release.

Steps

1. Create a ChatDrawer component

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

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

export function ChatDrawer({ isOpen, onClose, targetUserId, targetGroupId }: ChatDrawerProps) {
  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]);

  return (
    <>
      {/* Backdrop */}
      {isOpen && (
        <div
          onClick={onClose}
          style={{
            position: "fixed",
            inset: 0,
            zIndex: 999,
            backgroundColor: "rgba(0, 0, 0, 0.3)",
          }}
        />
      )}

      {/* Drawer */}
      {/*
        IMPORTANT: do NOT animate with `transform: translateX(...)`.
        `transform` on an element creates a new containing block for
        `position: fixed` descendants (per the CSS spec), so every
        fixed-positioned popover that CometChat renders inside the drawer
        — message options menu, emoji picker, file preview, reactions
        popover, thread panel — becomes anchored to the transformed
        drawer instead of the viewport. The result is popovers that
        appear clipped, offset, or drift as the drawer animates. Animate
        the `right` offset instead; no transform, no containing-block
        takeover, fixed popovers stay anchored to the viewport.
      */}
      <div
        style={{
          position: "fixed",
          top: 0,
          // Breakpoint-aware: full-width on mobile (≤ 640px), 400px on desktop.
          // The "-100%" closed offset slides the drawer off-screen at any width,
          // so mobile-full-screen works without recomputing the offset (ENG-35715).
          right: isOpen ? 0 : "-100%",
          bottom: 0,
          width: "min(400px, 100vw)",
          maxWidth: "100vw",
          zIndex: 1000,
          backgroundColor: "var(--cometchat-background-color-01, #fff)",
          boxShadow: "-4px 0 20px rgba(0, 0, 0, 0.15)",
          display: "flex",
          flexDirection: "column",
          transition: "right 0.3s ease-in-out",
        }}
      >
        {/* Header with close button */}
        <div style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          padding: "8px 12px",
          borderBottom: "1px solid #eee",
        }}>
          <span style={{ fontWeight: 600 }}>Chat</span>
          <button
            onClick={onClose}
            style={{
              background: "none",
              border: "none",
              fontSize: 18,
              cursor: "pointer",
              padding: "4px 8px",
            }}
            aria-label="Close chat"
          >
            X
          </button>
        </div>

        {/* Chat content */}
        {loading ? (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
            Loading...
          </div>
        ) : (
          <>
            {user && <CometChatMessageHeader user={user} />}
            {group && <CometChatMessageHeader group={group} />}
            <div style={{ flex: 1, overflow: "hidden" }}>
              {user && <CometChatMessageList user={user} hideReplyInThreadOption />}
              {group && <CometChatMessageList group={group} hideReplyInThreadOption />}
            </div>
            {user && <CometChatMessageComposer user={user} />}
            {group && <CometChatMessageComposer group={group} />}
          </>
        )}
      </div>
    </>
  );
}

2. Wire the trigger

Same approach as the modal -- find the right trigger point in the existing project:

import { useState } from "react";
import { ChatDrawer } from "./ChatDrawer";

function UserProfile({ userId }) {
  const [showChat, setShowChat] = useState(false);

  return (
    <div>
      {/* existing profile content */}
      <button onClick={() => setShowChat(true)}>
        Chat with this user
      </button>
      <ChatDrawer
        isOpen={showChat}
        onClose={() => setShowChat(false)}
        targetUserId={userId}
      />
    </div>
  );
}

3. Multi-conversation drawer variant

For a drawer that shows the full conversation list (not just a single thread):

// ConversationDrawer.tsx -- shows conversation list + message view
import { useState } from "react";
import {
  CometChatConversations,
  CometChatMessageHeader,
  CometChatMessageList,
  CometChatMessageComposer,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";

interface ConversationDrawerProps {
  isOpen: boolean;
  onClose: () => void;
}

export function ConversationDrawer({ isOpen, onClose }: ConversationDrawerProps) {
  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);
    }
  }

  const hasSelection = selectedUser || selectedGroup;

  return (
    <>
      {isOpen && (
        <div
          onClick={onClose}
          style={{ position: "fixed", inset: 0, zIndex: 999, backgroundColor: "rgba(0,0,0,0.3)" }}
        />
      )}
      {/*
        Animate the `right` offset, never `transform: translateX(...)` —
        `transform` creates a new containing block, which re-anchors
        CometChat's fixed-positioned popovers (emoji picker, message
        options, file preview, thread panel) to the drawer instead of
        the viewport and makes them misalign.
      */}
      <div
        style={{
          position: "fixed",
          top: 0,
          right: isOpen ? 0 : "-720px",  // off-screen by widest width when closed
          bottom: 0,
          width: hasSelection ? "720px" : "360px",
          maxWidth: "100vw",
          zIndex: 1000,
          backgroundColor: "var(--cometchat-background-color-01, #fff)",
          boxShadow: "-4px 0 20px rgba(0,0,0,0.15)",
          display: "flex",
          transition: "right 0.3s ease-in-out, width 0.3s ease-in-out",
        }}
      >
        {/* Conversation list */}
        <div style={{ width: "360px", borderRight: hasSelection ? "1px solid #eee" : "none", display: "flex", flexDirection: "column" }}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 12px", borderBottom: "1px solid #eee" }}>
            <span style={{ fontWeight: 600 }}>Messages</span>
            <button onClick={onClose} style={{ background: "none", border: "none", fontSize: 18, cursor: "pointer" }} aria-label="Close">&times;</button>
          </div>
          <div style={{ flex: 1 }}>
            <CometChatConversations onItemClick={handleConversationClick} />
          </div>
        </div>

        {/* Message view */}
        {hasSelection && (
          <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
            {selectedUser && <CometChatMessageHeader user={selectedUser} />}
            {selectedGroup && <CometChatMessageHeader group={selectedGroup} />}
            <div style={{ flex: 1, overflow: "hidden" }}>
              {selectedUser && <CometChatMessageList user={selectedUser} hideReplyInThreadOption />}
              {selectedGroup && <CometChatMessageList group={selectedGroup} hideReplyInThreadOption />}
            </div>
            {selectedUser && <CometChatMessageComposer user={selectedUser} />}
            {selectedGroup && <CometChatMessageComposer group={selectedGroup} />}
          </div>
        )}
      </div>
    </>
  );
}

Floating widget

A button-in-the-corner chat overlay. Available on every page of the app without a dedicated route. Common for support widgets, helpdesk chat, "contact us" overlays.

When to use: chat is a secondary concern (not the core product), and should be accessible from anywhere without navigating. When NOT to use: if chat is central to the app, use a route placement instead — widgets don't scale to inbox-style usage.

Steps

1. Create the ChatWidget component

// src/components/ChatWidget.tsx (or components/ChatWidget.tsx in Next.js)
import { useState } from "react";
import {
  CometChatConversations,
  CometChatMessageHeader,
  CometChatMessageList,
  CometChatMessageComposer,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";

export default function ChatWidget() {
  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState<CometChat.User | CometChat.Group>();

  function handleConversationClick(conv: CometChat.Conversation) {
    const entity = conv.getConversationWith();
    if (entity instanceof CometChat.User || entity instanceof CometChat.Group) {
      setSelected(entity);
    }
  }

  return (
    <>
      {/* Floating trigger button — always visible */}
      <button
        type="button"
        aria-label={open ? "Close chat" : "Open chat"}
        onClick={() => setOpen((v) => !v)}
        style={{
          position: "fixed",
          bottom: 24,
          right: 24,
          width: 56,
          height: 56,
          borderRadius: "50%",
          background: "var(--cometchat-primary-color, #6c63ff)",
          color: "white",
          border: "none",
          cursor: "pointer",
          fontSize: 24,
          boxShadow: "0 4px 16px rgba(0, 0, 0, 0.15)",
          zIndex: 9999,
        }}
      >
        {open ? "×" : "💬"}
      </button>

      {/* Widget panel — overlay, not a full-page drawer */}
      {open && (
        <div
          style={{
            position: "fixed",
            bottom: 96,  // leave room for the button
            right: 24,
            width: "min(380px, calc(100vw - 48px))",
            height: "min(600px, calc(100vh - 120px))",
            background: "var(--cometchat-background-color-01, white)",
            border: "1px solid var(--cometchat-border-color-light, #eee)",
            borderRadius: 12,
            boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
            display: "flex",
            flexDirection: "column",
            overflow: "hidden",
            zIndex: 9998,
          }}
        >
          {selected ? (
            <>
              <button
                onClick={() => setSelected(undefined)}
                style={{ alignSelf: "flex-start", background: "none", border: "none", padding: 12, cursor: "pointer" }}
              >
                ← Back
              </button>
              {selected instanceof CometChat.User && (
                <>
                  <CometChatMessageHeader user={selected} />
                  <div style={{ flex: 1, overflow: "hidden" }}>
                    <CometChatMessageList user={selected} hideReplyInThreadOption />
                  </div>
                  <CometChatMessageComposer user={selected} />
                </>
              )}
              {selected instanceof CometChat.Group && (
                <>
                  <CometChatMessageHeader group={selected} />
                  <div style={{ flex: 1, overflow: "hidden" }}>
                    <CometChatMessageList group={selected} hideReplyInThreadOption />
                  </div>
                  <CometChatMessageComposer group={selected} />
                </>
              )}
            </>
          ) : (
            <CometChatConversations onItemClick={handleConversationClick} />
          )}
        </div>
      )}
    </>
  );
}

Key points:

  • Button and panel are both position: fixed. z-index: 9999 / 9998 ensures they float above the app's content but don't conflict with modals (which typically go to z-index: 10000+).
  • Width/height clamp against viewport (min(...)) so the widget shrinks gracefully on mobile. On screens < 428px you may want to switch to full-screen (width: 100vw; height: 100vh; bottom: 0; right: 0) — gate with a useEffect + window.innerWidth or CSS media query.
  • The panel renders conversation list by default; clicking a conversation swaps to the single-thread view with a back arrow. Mirrors WhatsApp / iMessage widget UX.
  • Wrap everything in {open && (...)} rather than animating transforms — the CometChat components subscribe to SDK events on mount, so keeping the panel in the tree when closed wastes resources.

2. Mount at the app root

Where to mount depends on the framework. The widget must be a sibling (not a descendant) of the app's main layout so its fixed positioning escapes any overflow-hidden containers.

Framework Mount location
React (Vite / CRA) src/main.tsx or src/App.tsx, sibling of <Routes>
Next.js App Router app/layout.tsx, sibling of {children} (inside <body>, outside any main container with overflow: hidden)
Next.js Pages Router pages/_app.tsx, sibling of <Component />
React Router app/root.tsx (v7) or wherever <Outlet /> lives (v6)
Astro Inside a client:only="react" island in src/layouts/BaseLayout.astro

Example — Next.js App Router:

// app/layout.tsx
import ChatWidget from "@/components/ChatWidget";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <ChatWidget />
      </body>
    </html>
  );
}

In the Pages Router and Vite cases, the provider (CometChatProvider) must wrap both the app content and the widget — otherwise the widget won't have access to the init'd SDK.

3. Conditional rendering per route (optional)

Most apps want to hide the widget on auth pages (login, signup, password reset). Options:

React Router / Next.js — use useLocation() / usePathname():

// Wrap the export with a router-aware gate
import { useLocation } from "react-router-dom"; // or `usePathname` in Next.js

export default function ChatWidgetGate() {
  const pathname = useLocation().pathname;
  const hiddenOn = ["/login", "/signup", "/forgot-password"];
  if (hiddenOn.some((p) => pathname.startsWith(p))) return null;
  return <ChatWidget />;
}

Astro — render the island only on the pages that want it, rather than globally in the layout.

4. Feature toggle via config

Record the choice in .cometchat/config.json so the integration skill knows the widget was used:

npx @cometchat/skills-cli config save --placement widget --json

Do not invoke cometchat add-widget — that's a v2 CLI command that writes a template-based widget. v3 skills write the widget directly using the pattern above, so it fits the project's existing code style. add-widget is retained only for backward compatibility with v2 integrations.


Embedded placement

Chat embedded directly in an existing page, alongside other content. Common for marketplace product pages, dashboards, or support panels.

Steps

1. Create a ChatPanel component

The critical thing about embedded placement is the container MUST have an explicit height. CometChat components fill 100% of their container -- if the container has no height constraint, the components either collapse to zero or overflow the page.

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

interface ChatPanelProps {
  targetUserId?: string;
  targetGroupId?: string;
  height?: string;       // e.g., "500px", "60vh"
  conversationMode?: boolean; // Show conversation list instead of single thread
}

export function ChatPanel({
  targetUserId,
  targetGroupId,
  height = "500px",
}: ChatPanelProps) {
  const [user, setUser] = useState<CometChat.User>();
  const [group, setGroup] = useState<CometChat.Group>();
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    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));
    }
  }, [targetUserId, targetGroupId]);

  return (
    <div
      style={{
        height,
        display: "flex",
        flexDirection: "column",
        border: "1px solid #eee",
        borderRadius: "var(--cometchat-border-radius-lg, 8px)",
        overflow: "hidden",
      }}
    >
      {loading ? (
        <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
          Loading chat...
        </div>
      ) : (
        <>
          {user && <CometChatMessageHeader user={user} />}
          {group && <CometChatMessageHeader group={group} />}
          <div style={{ flex: 1, overflow: "hidden" }}>
            {user && <CometChatMessageList user={user} hideReplyInThreadOption />}
            {group && <CometChatMessageList group={group} hideReplyInThreadOption />}
          </div>
          {user && <CometChatMessageComposer user={user} />}
          {group && <CometChatMessageComposer group={group} />}
        </>
      )}
    </div>
  );
}

2. Find the container in the existing page

Read the existing page where chat should be embedded. Understand the layout before adding anything. Common patterns:

  • Product page: Chat panel below or beside the product description
  • Dashboard: Chat panel in a sidebar or a dashboard card
  • Profile page: Chat panel as a tab or section

3. Render the panel with a target

Connect the target user/group based on the page's data:

// Example: embedding on a product page
function ProductPage({ product }) {
  return (
    <div style={{ display: "flex", gap: 24 }}>
      <div style={{ flex: 1 }}>
        <h1>{product.name}</h1>
        <p>{product.description}</p>
        {/* other product content */}
      </div>
      <div style={{ width: 400 }}>
        <h3>Chat with the seller</h3>
        <ChatPanel
          targetUserId={product.sellerId}
          height="500px"
        />
      </div>
    </div>
  );
}

4. Conversation-list embedded panel

If you want an embedded panel with a conversation list (not just a single thread), use the multi-conversation composition from cometchat-components inside a container with explicit height:

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

interface EmbeddedInboxProps {
  height?: string;
}

export function EmbeddedInbox({ height = "600px" }: EmbeddedInboxProps) {
  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={{ height, display: "flex", border: "1px solid #eee", borderRadius: 8, overflow: "hidden" }}>
      <div style={{ width: "300px", borderRight: "1px solid #eee" }}>
        <CometChatConversations onItemClick={handleConversationClick} />
      </div>
      <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
        {selectedUser && (
          <>
            <CometChatMessageHeader user={selectedUser} />
            <div style={{ flex: 1, overflow: "hidden" }}>
              <CometChatMessageList user={selectedUser} hideReplyInThreadOption />
            </div>
            <CometChatMessageComposer user={selectedUser} />
          </>
        )}
        {selectedGroup && (
          <>
            <CometChatMessageHeader group={selectedGroup} />
            <div style={{ flex: 1, overflow: "hidden" }}>
              <CometChatMessageList group={selectedGroup} hideReplyInThreadOption />
            </div>
            <CometChatMessageComposer group={selectedGroup} />
          </>
        )}
        {!selectedUser && !selectedGroup && (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "#999" }}>
            Select a conversation
          </div>
        )}
      </div>
    </div>
  );
}

Hard rules

These rules apply to ALL placement patterns. Violating any of them causes integration bugs or destroys the user's existing project.

  1. NEVER replace the project's existing entry file (App.tsx, page.tsx, layout.tsx, main.tsx, etc.) unless the user explicitly chose "demo mode." Replacing the entry file destroys all existing functionality.

  2. ALWAYS read the project's existing files before deciding where to put things. Do not assume a project structure. Read the router config, the nav component, the layout files. Understand what exists before adding to it.

  3. ALWAYS create new files alongside existing ones. Do not modify files you do not fully understand. The exceptions are:

    • The router config (to add a route) -- read it first
    • The nav component (to add a link) -- read it first
    • The root CSS file (to add the css-variables.css import) -- read it first
  4. CometChat CSS must be imported exactly once. Before adding the import, check if it already exists:

    grep -r "css-variables.css" src/ app/ pages/ 2>/dev/null

    If it is already imported, do not add a duplicate.

  5. CometChatProvider or init must be at the app root, not inside a modal/drawer/panel. Re-initializing on every open causes flicker, dropped WebSocket connections, and race conditions.

  6. Every CometChat container must have explicit dimensions. Components fill 100% of their parent. If the parent has no height, the components collapse to zero. Always set height, min-height, or use flex/grid layout with a bounded container.

6a. Flex parents that hold a CometChatMessageList MUST set minHeight: 0 (and minWidth: 0 for horizontal flex parents). The W3C default min-height: auto makes flex children refuse to shrink below their intrinsic content size — so once the conversation grows past the viewport, the list pushes the composer below the fold and the layout breaks. This is the canonical chat-layout bug — works fine for short conversations, breaks for long ones. The fix is one CSS property; the diagnosis takes hours if you don't know the rule.

/* ✓ CORRECT — header + list + composer with the flex-shrink trap fixed */
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
  <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
    <div style={{ flex: "0 0 auto" }}>
      <CometChatMessageHeader user={user} />
    </div>
    <div style={{ flex: "1 1 0", minHeight: 0, overflow: "hidden" }}>
      <CometChatMessageList user={user} hideReplyInThreadOption />
    </div>
    <div style={{ flex: "0 0 auto" }}>
      <CometChatMessageComposer user={user} />
    </div>
  </div>
</div>

/* ✗ WRONG — list grows past the viewport once messages exceed visible area */
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
  <CometChatMessageHeader user={user} />
  <CometChatMessageList user={user} />          {/* takes intrinsic height, no scroll */}
  <CometChatMessageComposer user={user} />     {/* falls off the bottom */}
</div>

The wrap-each-component-in-a-flex-sized-div pattern is what makes this work. Don't pass the kit components directly as flex children — wrap them.

  1. Resolve target users/groups before rendering CometChat components. Use CometChat.getUser(uid) or CometChat.getGroup(guid) to get the full CometChat.User or CometChat.Group object. Do not pass a raw UID string to user props -- they expect object instances.

  2. For SSR frameworks, wrap CometChat components appropriately. See the cometchat-core skill, section 5 (SSR safety), for framework-specific patterns.

  3. Every <CometChatMessageList> MUST include hideReplyInThreadOption unless the integration also wires a thread panel (CometChatThreadHeader + scoped MessageList + scoped MessageComposer with parentMessageId). The kit's default (false) puts a "Reply in Thread" entry in the message action menu that silently does nothing when no panel is wired. Drawer, widget, modal, and embed patterns never wire a thread panel, so the prop is mandatory there. Two-pane route patterns (full messenger, social) MAY omit it if-and-only-if they implement the full thread-panel plumbing; otherwise keep it. Writing <CometChatMessageList user={user} /> without the prop in a drawer or widget is a generation bug — every example in this skill includes it for a reason.

  4. Never animate a CometChat-containing element with transform, translate-*, or any transition-transform. This includes:

    • Inline transform: translateX(...), transform: scale(...), etc.
    • Tailwind utilities translate-x-*, -translate-x-*, translate-x-0, translate-x-full, translate-y-*, scale-*, rotate-*, skew-*, transform-*
    • transition-transform, motion-safe:translate-*, will-change: transform
    • filter, perspective, backdrop-filter

    Any of these creates a new containing block for position: fixed descendants, which reparents CometChat's fixed-positioned popovers (emoji picker, message options menu, file preview, reactions popover, thread panel) to the animated container instead of the viewport. Animate the right / left offset instead (inline: right: isOpen ? 0 : '-420px'; Tailwind: right-0 / right-[-420px] with transition-[right]). See cometchat-core § 8 anti-pattern 11 for the full CSS-spec explanation.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/cometchat-cometchat-skills-cometchat-placement/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

cometchat-cometchat-skills-cometchat-placement.ocm.jsonjson
{
  "ocm": "1",
  "id": "cometchat-cometchat-skills-cometchat-placement",
  "kind": "skill",
  "name": "cometchat-placement",
  "description": "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.",
  "publisher": "cometchat",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "general"
    ],
    "tags": [
      "skill-md",
      "chat",
      "cometchat",
      "react",
      "placement",
      "route",
      "modal",
      "drawer",
      "widget",
      "embedded"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "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."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "skills.sh",
      "repository": "https://github.com/cometchat/cometchat-skills",
      "path": "skills/cometchat-placement/SKILL.md",
      "ref": "HEAD",
      "url": "https://github.com/cometchat/cometchat-skills/blob/HEAD/skills/cometchat-placement/SKILL.md",
      "key": "cometchat/cometchat-skills/skills/cometchat-placement/SKILL.md"
    },
    "compatibility": "@cometchat/chat-uikit-react ^6; @cometchat/chat-sdk-javascript ^4",
    "license": "MIT"
  },
  "instructions": "> **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.\n\n## Purpose\n\nThis 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 st",
  "cost": {
    "context_tokens": 13951
  }
}

Fetch it by URL: GET /api/v1/registry/cometchat-cometchat-skills-cometchat-placement/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.