cometchat-placement
SkillAI & modelsProduction 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.
No other account needed.
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@^6components 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-corefor initialization, login, CSS, and provider patterns - Read
cometchat-componentsfor 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(notCometChatMessageComposer) 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; preferCometChatCompactMessageComposerfor the split/two-pane layouts below andCometChatMessageComposerfor 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:
- Remove
hideReplyInThreadOptionfrom the main<CometChatMessageList> - Add
onThreadRepliesClickto capture the thread message - Render the thread panel (see
cometchat-components§ Threading for the full pattern —CometChatThreadHeader+ scopedMessageList+ scopedMessageComposerwithparentMessageId)
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
CometChatProviderinside the existing layout, wrapping the router outlet or children. - Import
@cometchat/chat-uikit-react/css-variables.cssat 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.tsxorsrc/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>inApp.tsxor a routes file - Next.js App Router:
app/directory -- just creating the page file atapp/chat/page.tsxIS the route - Next.js Pages Router:
pages/directory -- creatingpages/chat.tsxIS the route - Astro:
src/pages/directory -- creatingsrc/pages/chat.astroIS the route - React Router v7: File-based routing in
app/routes/or manual routes inapp/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