Build a Chat Panel

Last updated Oct 19, 2026 Prerequisite

This guide builds on top of the Build Your Own UI. It is recommended to read that guide first. Portions of the code will not be repeated here for brevity.

The chat components on this page require RealtimeKit Web UI Kit 1.1.0 or later and work best with RealtimeKit Web Core 1.2.4 or later.

The UI Kit includes a full chat panel and lower-level chat components. The chat components load messages in pages, instead of loading the entire chat history when they join. This keeps chat responsive in meetings with many participants or long chat history.

Use the full panel when you want RealtimeKit to manage chat behavior for you.

Use lower-level components when you need to control the layout around message lists, pinned messages, or the composer.

This page is not available for the Mobileplatform.

WebMobile

ReactWeb ComponentsAngular

Render the full chat panel

Use rtk-chat to render the full chat panel. It includes public chat, private chat, paginated messages, pinned messages, file uploads, image uploads, edit actions, delete actions, and emoji input.

<rtk-ui-provider id="rtk-ui-provider">
    <rtk-chat style="height: 480px;"></rtk-chat>
</rtk-ui-provider>

To set chat UI overrides, assign the overrides property on rtk-ui-provider or rtk-meeting.

const provider = document.querySelector("rtk-ui-provider");

provider.overrides = {
    disableEmojiPicker: true,
    disablePrivateChat: true,
};

The disableEmojiPicker override hides the emoji picker button. The disablePrivateChat override hides private chat even when the preset allows it.

To control private chat for all participants, prefer preset permissions.

Use RtkChat to render the full chat panel. It includes public chat, private chat, paginated messages, pinned messages, file uploads, image uploads, edit actions, delete actions, and emoji input.

import { RtkChat, RtkUiProvider } from "@cloudflare/realtimekit-react-ui";

function ChatPanel({ meeting }) {
    return (
        <RtkUiProvider
            meeting={meeting}
            overrides={{
                disableEmojiPicker: true,
                disablePrivateChat: true,
            }}
        >
            <RtkChat style={{ height: "480px" }} />
        </RtkUiProvider>
    );
}

The disableEmojiPicker override hides the emoji picker button. The disablePrivateChat override hides private chat even when the preset allows it.

To control private chat for all participants, prefer preset permissions.

Use rtk-chat to render the full chat panel. It includes public chat, private chat, paginated messages, pinned messages, file uploads, image uploads, edit actions, delete actions, and emoji input.

<rtk-ui-provider [meeting]="meeting" [overrides]="chatOverrides">
    <rtk-chat style="height: 480px;"></rtk-chat>
</rtk-ui-provider>
chatOverrides = {
    disableEmojiPicker: true,
    disablePrivateChat: true,
};

The disableEmojiPicker override hides the emoji picker button. The disablePrivateChat override hides private chat even when the preset allows it.

To control private chat for all participants, prefer preset permissions.

Build a custom chat layout

You can create your own chat layout from the same UI Kit components that RtkChat uses. Place each component wherever your design needs it, such as the message list in a sidebar and the composer in a footer, and connect them through your own state.

Component What it renders
RtkChatSelector A dropdown to switch between public chat and private conversations. Emits rtkChatSelectorChange with the selected participant.
RtkPinnedMessageSelector A collapsible header that expands to show pinned messages.
RtkChatMessagesUiPaginated The message list. Loads public messages when privateChatRecipient is empty, and the private conversation when you pass a participant.
RtkChatComposerView The message input, with file, image, and emoji buttons.

When you build your own layout, your code connects the components to meeting.chat:

  • RtkChatMessagesUiPaginated emits pinMessage, editMessage, and deleteMessage events from its message menu but does not act on them. Handle these events and call the matching meeting.chat methods.
  • RtkChatComposerView emits newMessage and editMessage events. Send or edit the message with meeting.chat.
  • Pass meeting.chat.maxTextLimit and meeting.chat.rateLimits to RtkChatComposerView so the composer enforces the same limits as the meeting.

The following example stacks the components vertically. Rearrange them to fit your layout.

import {
    RtkChatComposerView,
    RtkChatMessagesUiPaginated,
    RtkChatSelector,
    RtkPinnedMessageSelector,
} from "@cloudflare/realtimekit-react-ui";
import { useState } from "react";

function CustomChat({ meeting }) {
    const [privateChatRecipient, setPrivateChatRecipient] = useState(null);
    const [editingMessage, setEditingMessage] = useState(null);
    const { chatPublic, chatPrivate } = meeting.self.permissions;
    const publicChat = {
        canSendText: chatPublic.canSend && chatPublic.text,
        canSendFiles: chatPublic.canSend && chatPublic.files,
    };
    const privateChat = {
        canSendText: Boolean(chatPrivate?.canSend && chatPrivate?.text),
        canSendFiles: Boolean(chatPrivate?.canSend && chatPrivate?.files),
    };
    const activeChat = privateChatRecipient ? privateChat : publicChat;

    return (
        <div style={{ height: "480px" }}>
            <RtkChatSelector
                meeting={meeting}
                onRtkChatSelectorChange={(event) => {
                    setPrivateChatRecipient(event.detail.selectedUser ?? null);
                }}
            />
            <RtkPinnedMessageSelector meeting={meeting} />
            <RtkChatMessagesUiPaginated
                meeting={meeting}
                privateChatRecipient={privateChatRecipient}
                onPinMessage={(event) => {
                    const message = event.detail;
                    if (message.pinned) {
                        meeting.chat.unpin(message.id);
                    } else {
                        meeting.chat.pin(message.id);
                    }
                }}
                onEditMessage={(event) => {
                    if (event.detail.type === "text") {
                        setEditingMessage(event.detail);
                    }
                }}
                onDeleteMessage={(event) => {
                    if (editingMessage?.id === event.detail.id) {
                        setEditingMessage(null);
                    }
                    meeting.chat.deleteMessage(event.detail.id);
                }}
            />
            <RtkChatComposerView
                canSendTextMessage={activeChat.canSendText}
                canSendFiles={activeChat.canSendFiles}
                maxLength={meeting.chat.maxTextLimit}
                rateLimits={meeting.chat.rateLimits}
                inputTextPlaceholder="Enter a message"
                isEditing={!!editingMessage}
                message={editingMessage ? editingMessage.message : ""}
                storageKey={
                    editingMessage
                        ? `custom-chat-edit-${editingMessage.id}`
                        : "custom-chat-draft"
                }
                onNewMessage={(event) =>
                    meeting.chat.sendMessage(
                        event.detail,
                        privateChatRecipient ? [privateChatRecipient.id] : [],
                    )
                }
                onEditMessage={async (event) => {
                    try {
                        await meeting.chat.editTextMessage(editingMessage.id, event.detail);
                    } finally {
                        setEditingMessage(null);
                    }
                }}
                onEditCancel={() => setEditingMessage(null)}
            />
        </div>
    );
}

Refer to the RtkChat, RtkChatSelector, RtkPinnedMessageSelector, RtkChatMessagesUiPaginated, and RtkChatComposerView API references for component props.

Was this helpful?

YesNo

On this page

Build a Chat Panel - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/chat-panel/#page","headline":"Build a Chat Panel","description":"Build a scalable chat panel for your custom RealtimeKit meeting UI with paginated UI Kit chat components.","url":"https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/chat-panel/","inLanguage":"en","image":"https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/chat-panel/og.png?v=ea17480b73fe9a5a","dateModified":"2026-10-19","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Browse prebuilt RealtimeKit UI Kit components for building custom meeting interfaces. title: Component Library image: https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/component-library/og.png?v=a74842d247ec418b


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/realtime/llms.txt
Use this file to discover all available pages before exploring further.