> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tab-Based Chat

> Build a tab-based messaging UI with chats, calls, users, and groups in React Router with CometChat UI Kit.

<Accordion title="AI Integration Quick Reference">
  | Field        | Value                                                                                                                                                            |
  | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | Package      | `@cometchat/chat-uikit-react`                                                                                                                                    |
  | Framework    | React Router                                                                                                                                                     |
  | Components   | `CometChatConversations`, `CometChatCallLogs`, `CometChatUsers`, `CometChatGroups`, `CometChatMessageHeader`, `CometChatMessageList`, `CometChatMessageComposer` |
  | Layout       | Tabbed sidebar (Chats, Calls, Users, Groups) + message view                                                                                                      |
  | Prerequisite | Complete [React Router Integration](/ui-kit/react/react-router-integration) Steps 1–5 first                                                                      |
  | SSR          | Lazy import + mounted check — CometChat requires browser APIs                                                                                                    |
  | Pattern      | Full-featured messaging app with multiple sections                                                                                                               |
</Accordion>

This guide builds a tabbed messaging UI — Chats, Calls, Users, and Groups tabs in the sidebar, with a message view on the right. Good for full-featured apps that need more than just conversations.

This assumes you've already completed [React Router Integration](/ui-kit/react/react-router-integration) (project created, UI Kit installed, CSS imported).

<Frame>
  <img src="https://mintcdn.com/cometchat-22654f5b/8ODUflBxloB1jkgP/images/010808a2-multi_tab_ui_web_screens-4c6055da929b73c11d7d45b0112fd5fc.png?fit=max&auto=format&n=8ODUflBxloB1jkgP&q=85&s=1454a2402166c9939b57d59a834f2e32" width="1282" height="802" data-path="images/010808a2-multi_tab_ui_web_screens-4c6055da929b73c11d7d45b0112fd5fc.png" />
</Frame>

***

## What You're Building

Three sections working together:

1. **Tab bar** — switches between Chats, Calls, Users, and Groups
2. **Sidebar** — renders the list for the active tab
3. **Message view** — header + messages + composer for the selected item

***

## Step 1 — Create the Tab Component

<Tree>
  <Tree.Folder name="src" defaultOpen>
    <Tree.Folder name="app" defaultOpen>
      <Tree.Folder name="CometChatTabs" defaultOpen>
        <Tree.File name="CometChatTabs.tsx" />

        <Tree.File name="CometChatTabs.css" />
      </Tree.Folder>
    </Tree.Folder>
  </Tree.Folder>
</Tree>

Tab icons need to be placed in `public/assets/`. Download them from the [CometChat UI Kit assets folder on GitHub](https://github.com/cometchat/cometchat-uikit-react/tree/v6/sample-app/src/assets).

<Tree>
  <Tree.Folder name="public" defaultOpen>
    <Tree.Folder name="assets" defaultOpen>
      <Tree.File name="chats.svg" />

      <Tree.File name="calls.svg" />

      <Tree.File name="users.svg" />

      <Tree.File name="groups.svg" />
    </Tree.Folder>
  </Tree.Folder>
</Tree>

<Tabs>
  <Tab title="TypeScript">
    ```tsx title="CometChatTabs.tsx" lines theme={null}
    import { useState } from "react";
    import "./CometChatTabs.css";

    const chatsIcon = "/assets/chats.svg";
    const callsIcon = "/assets/calls.svg";
    const usersIcon = "/assets/users.svg";
    const groupsIcon = "/assets/groups.svg";

    export const CometChatTabs = (props: {
      onTabClicked?: (tabItem: { name: string; icon?: string }) => void;
      activeTab?: string;
    }) => {
      const { onTabClicked = () => {}, activeTab } = props;
      const [hoverTab, setHoverTab] = useState("");

      const tabItems = [
        { name: "CHATS", icon: chatsIcon },
        { name: "CALLS", icon: callsIcon },
        { name: "USERS", icon: usersIcon },
        { name: "GROUPS", icon: groupsIcon },
      ];

      return (
        <div className="cometchat-tab-component">
          {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)}
              >
                <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("")}
                />
                <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>
      );
    };
    ```
  </Tab>

  <Tab title="CSS">
    ```css title="CometChatTabs.css" lines theme={null}
    .cometchat-tab-component {
      display: flex;
      width: 100%;
      padding: 0px 8px;
      align-items: flex-start;
      gap: 8px;
      border-top: 1px solid var(--cometchat-border-color-light, #F5F5F5);
      border-right: 1px solid var(--cometchat-border-color-light, #F5F5F5);
      background: var(--cometchat-background-color-01, #FFF);
    }

    .cometchat-tab-component__tab {
      display: flex;
      padding: 12px 0px 10px 0px;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      gap: 4px;
      flex: 1 0 0;
      min-height: 48px;
    }

    .cometchat-tab-component__tab-icon {
      display: flex;
      width: 32px;
      height: 32px;
      justify-content: center;
      align-items: center;
      background: var(--cometchat-icon-color-secondary);
      -webkit-mask-size: contain;
      -webkit-mask-position: center;
      -webkit-mask-repeat: no-repeat;
      mask-size: contain;
      mask-position: center;
      mask-repeat: no-repeat;
      cursor: pointer;
    }

    .cometchat-tab-component__tab-text {
      color: var(--cometchat-text-color-secondary, #727272);
      text-align: center;
      font: var(--cometchat-font-caption1-medium, 500 12px Roboto);
      cursor: pointer;
    }

    .cometchat-tab-component__tab-icon-active {
      background: var(--cometchat-icon-color-highlight);
    }

    .cometchat-tab-component__tab-text-active {
      color: var(--cometchat-text-color-highlight);
    }
    ```
  </Tab>
</Tabs>

***

## Step 2 — Create the Sidebar Component

The sidebar renders the list for whichever tab is active, plus the tab bar at the bottom.

<Tree>
  <Tree.Folder name="src" defaultOpen>
    <Tree.Folder name="app" defaultOpen>
      <Tree.Folder name="CometChatSelector" defaultOpen>
        <Tree.File name="CometChatSelector.tsx" />

        <Tree.File name="CometChatSelector.css" />
      </Tree.Folder>
    </Tree.Folder>
  </Tree.Folder>
</Tree>

<Tabs>
  <Tab title="TypeScript">
    ```tsx title="CometChatSelector.tsx" lines theme={null}
    import { useEffect, useState } from "react";
    import {
      Call,
      Conversation,
      Group,
      User,
      CometChat,
    } from "@cometchat/chat-sdk-javascript";
    import {
      CometChatCallLogs,
      CometChatConversations,
      CometChatGroups,
      CometChatUIKitLoginListener,
      CometChatUsers,
    } from "@cometchat/chat-uikit-react";
    import { CometChatTabs } from "../CometChatTabs/CometChatTabs";

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

    export const CometChatSelector = (props: SelectorProps) => {
      const { onSelectorItemClicked = () => {} } = props;
      const [loggedInUser, setLoggedInUser] = useState<CometChat.User | null>();
      const [activeItem, setActiveItem] = useState<
        Conversation | User | Group | Call | undefined
      >();
      const [activeTab, setActiveTab] = useState<string>("chats");

      useEffect(() => {
        const user = CometChatUIKitLoginListener.getLoggedInUser();
        setLoggedInUser(user);
      }, []);

      return (
        <>
          {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");
                  }}
                />
              )}
            </>
          )}

          <CometChatTabs
            activeTab={activeTab}
            onTabClicked={(item) => setActiveTab(item.name.toLowerCase())}
          />
        </>
      );
    };
    ```
  </Tab>

  <Tab title="CSS">
    ```css title="CometChatSelector.css" lines theme={null}
    .selector-wrapper .cometchat-conversations .cometchat-list__header-menu .cometchat-button__icon {
      background: var(--cometchat-icon-color-primary);
    }

    .cometchat-conversations .cometchat-list__header-menu .cometchat-button__icon:hover {
      background: var(--cometchat-icon-color-highlight);
    }

    .cometchat-list__header-search-bar {
      border-right: none;
    }

    .cometchat .cometchat-menu-list__sub-menu-list-item {
      text-align: left;
    }

    .cometchat .cometchat-conversations .cometchat-menu-list__sub-menu-list {
      width: 212px;
      top: 40px !important;
      left: 172px !important;
    }

    #logged-in-user {
      border-bottom: 2px solid var(--cometchat-border-color-default, #E8E8E8);
    }

    #logged-in-user .cometchat-menu-list__sub-menu-item-title,
    #logged-in-user .cometchat-menu-list__sub-menu-list-item {
      cursor: default;
    }

    .cometchat-menu-list__sub-menu-list-item-icon-log-out {
      background-color: var(--cometchat-error-color, #F44649);
    }

    .cometchat-menu-list__sub-menu-item-title-log-out {
      color: var(--cometchat-error-color, #F44649);
    }

    .chat-menu .cometchat .cometchat-menu-list__sub-menu-item-title {
      cursor: pointer;
    }

    .chat-menu .cometchat .cometchat-menu-list__sub-menu {
      box-shadow: none;
    }

    .chat-menu .cometchat .cometchat-menu-list__sub-menu-icon {
      background-color: var(--cometchat-icon-color-primary, #141414);
      width: 24px;
      height: 24px;
    }
    ```
  </Tab>
</Tabs>

***

## Step 3 — Create the CometChatNoSSR Component

This component handles init, login, and renders the full tabbed chat experience. It runs client-side only.

<Tree>
  <Tree.Folder name="src" defaultOpen>
    <Tree.Folder name="app" defaultOpen>
      <Tree.Folder name="CometChatNoSSR" defaultOpen>
        <Tree.File name="CometChatNoSSR.tsx" />

        <Tree.File name="CometChatNoSSR.css" />
      </Tree.Folder>
    </Tree.Folder>
  </Tree.Folder>
</Tree>

<Tabs>
  <Tab title="TypeScript">
    ```tsx title="CometChatNoSSR.tsx" lines highlight={15-17, 20} theme={null}
    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";

    // Replace with your actual credentials
    const COMETCHAT_CONSTANTS = {
      APP_ID: "",    // Replace with your App ID
      REGION: "",    // Replace with your Region
      AUTH_KEY: "",  // Replace with your Auth Key (dev only)
    };

    const UID = "cometchat-uid-4"; // Replace with your actual UID

    const CometChatNoSSR: React.FC = () => {
      const [user, setUser] = useState<CometChat.User | undefined>(undefined);
      const [selectedUser, setSelectedUser] = useState<CometChat.User | undefined>(undefined);
      const [selectedGroup, setSelectedGroup] = useState<CometChat.Group | undefined>(undefined);

      useEffect(() => {
        if (typeof window === "undefined") return;

        const UIKitSettings = new UIKitSettingsBuilder()
          .setAppId(COMETCHAT_CONSTANTS.APP_ID)
          .setRegion(COMETCHAT_CONSTANTS.REGION)
          .setAuthKey(COMETCHAT_CONSTANTS.AUTH_KEY)
          .subscribePresenceForAllUsers()
          .build();

        CometChatUIKit.init(UIKitSettings)
          ?.then(() => {
            console.log("Initialization completed successfully");
            CometChatUIKit.getLoggedinUser().then((loggedInUser) => {
              if (!loggedInUser) {
                CometChatUIKit.login(UID)
                  .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">
          <div className="conversations-wrapper">
            <CometChatSelector
              onSelectorItemClicked={(activeItem) => {
                let item = activeItem;
                if (activeItem instanceof CometChat.Conversation) {
                  item = activeItem.getConversationWith();
                }
                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>

          {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 a conversation to start chatting</div>
          )}
        </div>
      ) : undefined;
    };

    export default CometChatNoSSR;
    ```
  </Tab>

  <Tab title="CSS">
    ```css title="CometChatNoSSR.css" lines theme={null}
    .conversations-with-messages {
      display: flex;
      height: 100%;
      width: 100%;
    }

    .conversations-wrapper {
      height: 100%;
      width: 480px;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      height: inherit;
    }

    .conversations-wrapper > .cometchat {
      overflow: hidden;
    }

    .messages-wrapper {
      width: calc(100% - 480px);
      height: 100%;
      display: flex;
      flex-direction: column;
    }

    .empty-conversation {
      height: 100%;
      width: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      background: white;
      color: var(--cometchat-text-color-secondary, #727272);
      font: var(--cometchat-font-body-regular, 400 14px Roboto);
    }

    .cometchat .cometchat-message-composer {
      border-radius: 0px;
    }
    ```
  </Tab>
</Tabs>

***

## Step 4 — Disable SSR and Add the Route

Create `CometChat.tsx` inside the `routes` folder. This uses lazy loading and a mounted check to ensure CometChat only runs client-side.

```tsx title="routes/CometChat.tsx" lines theme={null}
import React, { lazy, Suspense, useEffect, useState } from "react";
import "@cometchat/chat-uikit-react/css-variables.css";

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>
  );
}
```

Add the route to your routes config:

```ts title="routes.ts" lines theme={null}
import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  index("routes/home.tsx"),
  route("chat", "routes/CometChat.tsx"),
] satisfies RouteConfig;
```

CometChat depends on browser APIs (`window`, `WebSocket`, `document`). The lazy import + mounted check ensures the component only renders on the client.

***

## Step 5 — Run the Project

<Tabs>
  <Tab title="npm">
    ```bash lines theme={null}
    npm run dev
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash lines theme={null}
    pnpm dev
    ```
  </Tab>

  <Tab title="yarn">
    ```bash lines theme={null}
    yarn dev
    ```
  </Tab>
</Tabs>

Navigate to `/chat` (e.g. `http://localhost:5173/chat`). You should see the tab bar at the bottom of the sidebar. Switch between Chats, Calls, Users, and Groups — tapping any item loads the message view on the right.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Theming" icon="paintbrush" href="/ui-kit/react/theme">
    Customize colors, fonts, and styles to match your brand
  </Card>

  <Card title="Components Overview" icon="grid-2" href="/ui-kit/react/components-overview">
    Browse all prebuilt UI components
  </Card>

  <Card title="React Router Integration" icon="react" href="/ui-kit/react/react-router-integration">
    Back to the main setup guide
  </Card>

  <Card title="Core Features" icon="comments" href="/ui-kit/react/core-features">
    Chat features included out of the box
  </Card>
</CardGroup>
