This guide walks you through creating a tab-based messaging UI using React and CometChat UIKit. The UI will include different sections for Chats, Calls, Users, and Groups, allowing seamless navigation.

User Interface Preview

This layout consists of:
  1. Sidebar (Conversation List) – Displays recent conversations with active users and groups.
  2. Message View – Shows the selected chat with real-time messages.
  3. Message Input Box – Allows users to send messages seamlessly.

Step-by-Step Guide

Step 1: Create a Tab Component

To manage navigation, let’s build a CometChatTabs component. This component will render different tabs and allow switching between sections dynamically.

Folder Structure

Create a CometChatTabs folder inside your src directory and add the following files:
public/
├── assets # These are the images you need to save
   ├── chats.svg 
   ├── calls.svg 
   ├── users.svg 
   ├── groups.svg
src/app/
│── CometChatTabs/
   ├── CometChatTabs.tsx
   ├── CometChatTabs.css 

Download the Icons

These icons are available in the CometChat UI Kit assets folder. You can find them at:
🔗 GitHub Assets Folder

Implementation

CometChatTabs.tsx
import { useState } from "react";
import "./CometChatTabs.css";

// Define icon paths for each tab
const chatsIcon = "/assets/chats.svg";
const callsIcon = "/assets/calls.svg";
const usersIcon = "/assets/users.svg";
const groupsIcon = "/assets/groups.svg";

// CometChatTabs component to display tab options
export const CometChatTabs = (props: {
  onTabClicked?: (tabItem: { name: string; icon?: string }) => void; // Callback when a tab is clicked
  activeTab?: string; // Name of the currently active tab
}) => {
  // Destructure props with default fallback
  const {
    onTabClicked = () => {}, // Fallback to no-op if not provided
    activeTab,
  } = props;

  // State to track the currently hovered tab
  const [hoverTab, setHoverTab] = useState("");

  // Array of tab items with their labels and icons
  const tabItems = [
    { name: "CHATS", icon: chatsIcon },
    { name: "CALLS", icon: callsIcon },
    { name: "USERS", icon: usersIcon },
    { name: "GROUPS", icon: groupsIcon },
  ];

  return (
    <div className="cometchat-tab-component">
      {/* Loop through each tab item to render it */}
      {tabItems.map((tabItem) => {
        const isActive =
          activeTab === tabItem.name.toLowerCase() ||
          hoverTab === tabItem.name.toLowerCase();

        return (
          <div
            key={tabItem.name}
            className="cometchat-tab-component__tab"
            onClick={() => onTabClicked(tabItem)} // Invoke callback on click
          >
            {/* Icon section with mask styling */}
            <div
              className={
                isActive
                  ? "cometchat-tab-component__tab-icon cometchat-tab-component__tab-icon-active"
                  : "cometchat-tab-component__tab-icon"
              }
              style={{
                WebkitMaskImage: `url(${tabItem.icon})`,
                maskImage: `url(${tabItem.icon})`,
              }}
              onMouseEnter={() => setHoverTab(tabItem.name.toLowerCase())}
              onMouseLeave={() => setHoverTab("")}
            />

            {/* Tab label */}
            <div
              className={
                isActive
                  ? "cometchat-tab-component__tab-text cometchat-tab-component__tab-text-active"
                  : "cometchat-tab-component__tab-text"
              }
              onMouseEnter={() => setHoverTab(tabItem.name.toLowerCase())}
              onMouseLeave={() => setHoverTab("")}
            >
              {tabItem.name}
            </div>
          </div>
        );
      })}
    </div>
  );
};

Step 2: Create Sidebar

Let’s create the Sidebar component which will render different conversations.

Folder Structure

Create a CometChatSelector folder inside your src/app directory and add the following files:
src/app/
│── CometChatSelector/ 
   ├── CometChatSelector.tsx
   ├── CometChatSelector.css
CometChatSelector.tsx
import { useEffect, useState } from "react";
import {
  Call,
  Conversation,
  Group,
  User,
  CometChat
} from "@cometchat/chat-sdk-javascript";

import {
  CometChatCallLogs,
  CometChatConversations,
  CometChatGroups,
  CometChatUIKit,
  CometChatUIKitLoginListener,
  CometChatUsers
} from "@cometchat/chat-uikit-react";

import { CometChatTabs } from "../CometChatTabs/CometChatTabs";

// Define props interface for selector
interface SelectorProps {
  onSelectorItemClicked?: (input: User | Group | Conversation | Call, type: string) => void;
}

export const CometChatSelector = (props: SelectorProps) => {
  const {
    onSelectorItemClicked = () => {},
  } = props;

  // State to manage currently logged in user
  const [loggedInUser, setLoggedInUser] = useState<CometChat.User | null>();

  // State to track selected conversation, user, group, or call
  const [activeItem, setActiveItem] = useState<
    Conversation | User | Group | Call | undefined
  >();

  // State to track currently active tab: "chats", "calls", "users", or "groups"
  const [activeTab, setActiveTab] = useState<string>("chats");

  // Fetch logged-in user once component mounts
  useEffect(() => {
    const user = CometChatUIKitLoginListener.getLoggedInUser();
    setLoggedInUser(user);
  }, [CometChatUIKitLoginListener?.getLoggedInUser()]);

  // Logout function to clear user session
  const logOut = () => {
    CometChatUIKit.logout()
      .then(() => {
        setLoggedInUser(null);
      })
      .catch((error) => {
        console.log("Logout error:", error);
      });
  };

  return (
    <>
      {/* Render selector content only if a user is logged in */}
      {loggedInUser && (
        <>
          {activeTab === "chats" && (
            <CometChatConversations
              activeConversation={activeItem instanceof CometChat.Conversation ? activeItem : undefined}
              onItemClick={(item) => {
                setActiveItem(item);
                onSelectorItemClicked(item, "updateSelectedItem");
              }}
            />
          )}

          {activeTab === "calls" && (
            <CometChatCallLogs
              activeCall={activeItem as Call}
              onItemClick={(item: Call) => {
                setActiveItem(item);
                onSelectorItemClicked(item, "updateSelectedItemCall");
              }}
            />
          )}

          {activeTab === "users" && (
            <CometChatUsers
              activeUser={activeItem as User}
              onItemClick={(item) => {
                setActiveItem(item);
                onSelectorItemClicked(item, "updateSelectedItemUser");
              }}
            />
          )}

          {activeTab === "groups" && (
            <CometChatGroups
              activeGroup={activeItem as Group}
              onItemClick={(item) => {
                setActiveItem(item);
                onSelectorItemClicked(item, "updateSelectedItemGroup");
              }}
            />
          )}
        </>
      )}

      {/* Render the tab switcher at the bottom */}
      <CometChatTabs
        activeTab={activeTab}
        onTabClicked={(item) => {
          setActiveTab(item.name.toLowerCase()); // Update tab on click
        }}
      />
    </>
  );
};

Step 3: Render Experience

Now we will update the CometChatNoSSR.tsx & CometChatNoSSR.css files to import these new components as below,
CometChatNoSSR.tsx
import React, { useEffect, useState } from "react";
import { 
    CometChatMessageComposer, 
    CometChatMessageHeader, 
    CometChatMessageList, 
    CometChatUIKit, 
    UIKitSettingsBuilder 
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";
import { CometChatSelector } from "../CometChatSelector/CometChatSelector";
import "./CometChatNoSSR.css";

// Constants for CometChat configuration
const COMETCHAT_CONSTANTS = {
  APP_ID: "",
  REGION: "",
  AUTH_KEY: "",
};

// Functional component for CometChatNoSSR
const CometChatNoSSR: React.FC = () => {
  // State to store the logged-in user
  const [user, setUser] = useState<CometChat.User | undefined>(undefined);
  // State to store selected user or group
  const [selectedUser, setSelectedUser] = useState<CometChat.User | undefined>(undefined);
  const [selectedGroup, setSelectedGroup] = useState<CometChat.Group | undefined>(undefined);

  useEffect(() => {
    // Initialize UIKit settings
    const UIKitSettings = new UIKitSettingsBuilder()
      .setAppId(COMETCHAT_CONSTANTS.APP_ID)
      .setRegion(COMETCHAT_CONSTANTS.REGION)
      .setAuthKey(COMETCHAT_CONSTANTS.AUTH_KEY)
      .subscribePresenceForAllUsers()
      .build();

    // Initialize CometChat UIKit
    CometChatUIKit.init(UIKitSettings)
      ?.then(() => {
        console.log("Initialization completed successfully");
        // Check if user is already logged in
        CometChatUIKit.getLoggedinUser().then((loggedInUser) => {
          if (!loggedInUser) {
            // Perform login if no user is logged in
            CometChatUIKit.login("cometchat-uid-4")
              .then((user) => {
                console.log("Login Successful", { user });
                setUser(user);
              })
              .catch((error) => console.error("Login failed", error));
          } else {
            console.log("Already logged-in", { loggedInUser });
            setUser(loggedInUser);
          }
        });
      })
      .catch((error) => console.error("Initialization failed", error));
  }, []);

  return user ? (
    <div className="conversations-with-messages">
      {/* Sidebar with conversation list */}
      <div className="conversations-wrapper">
        <CometChatSelector 
          onSelectorItemClicked={(activeItem) => {
            let item = activeItem;
            // Extract the conversation participant
            if (activeItem instanceof CometChat.Conversation) {
              item = activeItem.getConversationWith();
            } 
            // Update states based on the type of selected item
            if (item instanceof CometChat.User) {
              setSelectedUser(item as CometChat.User);
              setSelectedGroup(undefined);
            } else if (item instanceof CometChat.Group) {
              setSelectedUser(undefined);
              setSelectedGroup(item as CometChat.Group);
            } else {
              setSelectedUser(undefined);
              setSelectedGroup(undefined);
            }
          }} 
        />
      </div>
      
      {/* Message view section */}
      {selectedUser || selectedGroup ? (
        <div className="messages-wrapper">
          <CometChatMessageHeader user={selectedUser} group={selectedGroup} />
          <CometChatMessageList user={selectedUser} group={selectedGroup} />
          <CometChatMessageComposer user={selectedUser} group={selectedGroup} />
        </div>
      ) : (
        <div className="empty-conversation">Select Conversation to start</div>
      )}
    </div>
  ) : undefined;
};

export default CometChatNoSSR;

Step 4: Disable SSR and Render the CometChat Component

Create a file CometChat.tsx inside the routes folder:
import React, { lazy, Suspense, useEffect, useState } from "react";
import "@cometchat/chat-uikit-react/css-variables.css";

// Lazy import to prevent SSR crash
const CometChatNoSSR = lazy(() => import("../CometChatNoSSR/CometChatNoSSR"));

export default function CometChatRoute() {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  return mounted ? (
    <Suspense fallback={<div>Loading...</div>}>
      <CometChatNoSSR />
    </Suspense>
  ) : (
    <div>Loading...</div>
  );
}
Now, create a route for CometChat in your routes file:
import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  index("routes/home.tsx"),
  route("chat", "routes/CometChat.tsx"), // Chat Route
] satisfies RouteConfig;
Why disable SSR? CometChat Chat Builder relies on browser APIs such as window, document, and WebSockets. Since React Router renders on the server by default, disabling SSR for this component prevents runtime errors.

Step 5: Update App CSS

Next, add the following styles to app.css to ensure CometChat UI Kit is properly styled.
app.css
:root {
  --background: #ffffff;
  --foreground: #171717;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

/** Give your App a height of `100%`. Keep other CSS properties in the below selector as it is. */
.root {
  height: 100%;
}

html,
body {
  height: 100%;
}

html,
body {
  max-width: 100vw;
  overflow-x: hidden;
}

body {
  color: var(--foreground);
  background: var(--background);
  font-family: Arial, Helvetica, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

@media (prefers-color-scheme: dark) {
  html {
    color-scheme: dark;
  }
}

Step 6: Run Your Application

  1. Start the development server
    npm run dev
    
  2. Verify the chat interface
  • In your browser, navigate to the /chat route (e.g., http://localhost:3000/chat).
  • Confirm that the chat experience loads as expected.

Next Steps

Enhance the User Experience