Note:1. It is recommended to use npm for installation. npm will automatically download the required peerDependencies.2.npm create vite@latestuses the latest version of Vite. The latest Vite may conflict with older versions of Node.js. Please verify your environment configuration.
npm create vite@latest◇ Project name:│ chat-integration-react-h5│◇ Select a framework:│ React│◇ Select a variant:│ TypeScript + React Compiler│◇ Use ESLint instead of Oxlint?│ Yes (ESLint)│◇ Install with npm and start now?│ Yes
npm install @tencentcloud/chat-uikit-react @tencentcloud/uikit-base-component-react
<!DOCTYPE html><html lang="en-US"><head><meta charset="UTF-8" /><!-- H5 viewport: covers the notch, locks zoom for an app-like experience. --><metaname="viewport"content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"/><title>Chat UIKit H5 Quickstart (React)</title></head><body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body></html>
/* ------- Mobile baseline ------- */* {box-sizing: border-box;}html,body,#root {width: 100%;height: 100%;margin: 0;}body {/* dvh fallback handles the iOS Safari address bar. */min-height: 100vh;min-height: 100dvh;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;color: #1f2329;background: #f5f6f8;/* Remove the gray tap flash on mobile webkit. */-webkit-tap-highlight-color: transparent;}button {font: inherit;cursor: pointer;}/* 16px keeps iOS from auto-zooming when an input is focused. */input {font-size: 16px;}
import { createRoot } from 'react-dom/client';import App from './App';import './styles.css';createRoot(document.getElementById('root')!).render(<App />,);
import { useState } from 'react';import { LoginStatus, UIKitProvider, useLoginState } from '@tencentcloud/chat-uikit-react';import { LoginView } from './LoginView';import { ChatLayout } from './ChatLayout';import { languageResources } from './i18n';function App() {const { status } = useLoginState();const isLogined = status === LoginStatus.SUCCESS;// `language` controls both UIKit strings and demo strings.const [language, setLanguage] = useState('en-US');return (<UIKitProvider language={language} languageResources={languageResources}>{isLogined? <ChatLayout onChangeLanguage={setLanguage} />: <LoginView language={language} onChangeLanguage={setLanguage} />}</UIKitProvider>);}export default App;
import { useState, type SubmitEvent } from 'react';import { useLoginState, useUIKit } from '@tencentcloud/chat-uikit-react';import './LoginView.css';// Languages shown in the switcher. Labels stay in their own language so users// can always recognize their option regardless of the current UI language.const LANGUAGES = [{ value: 'en-US', label: 'English' },{ value: 'ko-KR', label: '한국인' },];interface LoginViewProps {language: string;onChangeLanguage: (language: string) => void;}export function LoginView({ language, onChangeLanguage }: LoginViewProps) {const { login } = useLoginState();const { t } = useUIKit();const [sdkAppId, setSdkAppId] = useState<number>();const [userId, setUserId] = useState('');const [userSig, setUserSig] = useState('');const [loading, setLoading] = useState(false);const [error, setError] = useState('');const handleSubmit = async (e: SubmitEvent) => {e.preventDefault();setError('');if (!sdkAppId || !userId || !userSig) {setError(t('demo.error.required'));return;}setLoading(true);try {await login({ SDKAppID: Number(sdkAppId), userID: userId, userSig });// On success, the login gate in App.tsx switches to ChatLayout automatically.} catch (err) {setError(err instanceof Error ? err.message : t('demo.error.loginFailed'));} finally {setLoading(false);}};return (<div className="login"><div className="login__lang" role="group" aria-label="Language">{LANGUAGES.map(item => (<buttonkey={item.value}type="button"className={item.value === language ? 'login__lang-btn login__lang-btn--active' : 'login__lang-btn'}onClick={() => onChangeLanguage(item.value)}>{item.label}</button>))}</div><form className="login__card" onSubmit={handleSubmit}><h1 className="login__title">{t('demo.appTitle')}</h1><label className="login__field"><span>{t('demo.field.sdkAppId')}</span><inputtype="number"placeholder={t('demo.placeholder.sdkAppId')}value={sdkAppId || ''}onChange={e => setSdkAppId(Number(e.target.value))}/></label><label className="login__field"><span>{t('demo.field.userId')}</span><inputtype="text"placeholder={t('demo.placeholder.userId')}value={userId}onChange={e => setUserId(e.target.value)}/></label><label className="login__field"><span>{t('demo.field.userSig')}</span><inputtype="text"placeholder={t('demo.placeholder.userSig')}value={userSig}onChange={e => setUserSig(e.target.value)}/></label>{error && <p className="login__error">{error}</p>}<button className="login__submit" type="submit" disabled={loading}>{loading ? t('demo.loggingIn') : t('demo.login')}</button></form></div>);}
.login {position: relative;display: flex;width: 100%;height: 100dvh;align-items: center;justify-content: center;padding: max(20px, env(safe-area-inset-top)) 20px max(20px, env(safe-area-inset-bottom));/* Soft natural-white backdrop with a subtle top-down gradient for depth. */background: linear-gradient(180deg, #ffffff 0%, #f3f5f8 100%);}/* Segmented language switcher, pinned to the top-right. */.login__lang {position: absolute;top: calc(16px + env(safe-area-inset-top));right: 16px;display: inline-flex;padding: 3px;border-radius: 999px;background: #eef0f3;}.login__lang-btn {min-width: 56px;padding: 6px 12px;border: 0;border-radius: 999px;background: transparent;color: #6b7280;font-size: 13px;font-weight: 500;transition: background 0.2s, color 0.2s;}.login__lang-btn--active {background: #fff;color: #1f2329;box-shadow: 0 1px 2px rgb(16 24 40 / 10%);}.login__card {display: flex;width: 100%;max-width: 360px;flex-direction: column;gap: 16px;padding: 28px 24px;border-radius: 16px;background: #fff;/* Layered, low-opacity shadow for a soft, natural lift off the white bg. */border: 1px solid rgb(16 24 40 / 4%);box-shadow:0 1px 2px rgb(16 24 40 / 4%),0 12px 32px rgb(16 24 40 / 8%);}.login__title {margin: 0 0 4px;font-size: 20px;font-weight: 600;text-align: center;}.login__field {display: flex;flex-direction: column;gap: 6px;font-size: 13px;color: #6b7280;}.login__field input {padding: 12px 14px;border: 1px solid #e8eaed;border-radius: 10px;outline: none;appearance: none;}.login__field input:focus {border-color: #1464ff;}.login__error {margin: 0;color: #e34d59;font-size: 13px;}.login__submit {padding: 13px;border: 0;border-radius: 10px;background: #1464ff;color: #fff;font-size: 16px;font-weight: 600;}.login__submit:disabled {opacity: 0.6;}
import { useEffect, useRef, useState, type ReactNode } from 'react';import {Chat,ChatHeaderH5,ChatSettingH5,ContactInfoH5,ContactListH5,ConversationListH5,MessageInputH5,MessageListH5,SearchH5,VariantType,useChatContext,useLoginState,useUIKit,} from '@tencentcloud/chat-uikit-react';import { IconBulletpoint, IconSearch } from '@tencentcloud/uikit-base-component-react';import './ChatLayout.css';type MobileView = 'conversations' | 'chat' | 'contacts' | 'contactInfo' | 'search' | 'setting';interface ChatLayoutProps {onChangeLanguage: (language: string) => void;}/** A full-screen view; only the active one is visible (kept mounted for state). */function Screen({ active, children }: { active: boolean; children: ReactNode }) {return (<section className="screen" style={{ zIndex: active ? 1 : 0, visibility: active ? 'visible' : 'hidden' }}>{children}</section>);}export function ChatLayout({ onChangeLanguage }: ChatLayoutProps) {const { activeConversationID, setActiveConversation } = useChatContext();const { loginUserInfo, logout } = useLoginState();const { t, language } = useUIKit();const [activeView, setActiveView] = useState<MobileView>('conversations');// Remember where 'search' was opened from, so its back button returns there.const [previousView, setPreviousView] = useState<MobileView>('chat');const activeViewRef = useRef(activeView);const prevConversationIDRef = useRef(activeConversationID);useEffect(() => {activeViewRef.current = activeView;}, [activeView]);useEffect(() => {const changed = prevConversationIDRef.current !== activeConversationID;prevConversationIDRef.current = activeConversationID;if (changed && activeConversationID && activeViewRef.current === 'conversations') {setActiveView('chat');}}, [activeConversationID]);const openFromChat = (view: 'search' | 'setting') => {setPreviousView(activeView);setActiveView(view);};const handleChatBack = () => {setActiveConversation(undefined);setActiveView('conversations');};return (<div className="app-shell"><Screen active={activeView === 'conversations'}><header className="topbar"><span className="topbar__title">{t('demo.chats')}{loginUserInfo?.userId ? ` · ${loginUserInfo.userId}` : ''}</span><div className="chat-pane__actions"><buttonclassName="topbar__text-btn"type="button"onClick={() => onChangeLanguage(language === 'zh-CN' ? 'en-US' : 'zh-CN')}>{language === 'en-US' ? 'EN' : 'KR'}</button><button className="topbar__text-btn" type="button" onClick={logout}>{t('demo.logout')}</button></div></header><div className="screen__body"><ConversationListH5style={{ height: '100%' }}onSelectConversation={() => setActiveView('chat')}/></div><nav className="tabbar"><button className="tabbar__item tabbar__item--active" type="button">{t('demo.chats')}</button><button className="tabbar__item" type="button" onClick={() => setActiveView('contacts')}>{t('demo.contacts')}</button></nav></Screen><Screen active={activeView === 'contacts'}><header className="topbar"><span className="topbar__title">{t('demo.contacts')}</span></header><div className="screen__body"><ContactListH5 onContactItemClick={() => setActiveView('contactInfo')} /></div><nav className="tabbar"><button className="tabbar__item" type="button" onClick={() => setActiveView('conversations')}>{t('demo.chats')}</button><button className="tabbar__item tabbar__item--active" type="button">{t('demo.contacts')}</button></nav></Screen><Screen active={activeView === 'contactInfo'}><div className="screen__body screen__body--surface"><ContactInfoH5onClose={() => setActiveView('contacts')}onSendMessage={() => setActiveView('chat')}onEnterGroup={() => setActiveView('chat')}/></div></Screen><Screen active={activeView === 'chat'}><Chat className="chat-pane"><ChatHeaderH5className="chat-pane__header"enableUserStatusonBack={handleChatBack}ChatHeaderRight={(<div className="chat-pane__actions"><button className="icon-btn" type="button" aria-label={t('demo.search')} onClick={() => openFromChat('search')}><IconSearch size="20px" /></button><button className="icon-btn" type="button" aria-label={t('demo.setting')} onClick={() => openFromChat('setting')}><IconBulletpoint size="20px" /></button></div>)}/><MessageListH5 /><MessageInputH5 /></Chat></Screen><Screen active={activeView === 'search'}><div className="screen__body"><SearchH5variant={VariantType.EMBEDDED}onBack={() => setActiveView(previousView)}onResultItemClick={(_item, type) => {if (type === 'chat_message') {setActiveView('chat');}}}/></div></Screen><Screen active={activeView === 'setting'}><div className="screen__body screen__body--surface"><ChatSettingH5 onBack={() => setActiveView('chat')} /></div></Screen></div>);}
/* ------- Shell + stacked screens ------- */.app-shell {position: relative;width: 100vw;height: 100vh;height: 100dvh;overflow: hidden;background: #fff;}/* Each full-screen view is stacked; only the active one is visible. */.screen {position: absolute;inset: 0;display: flex;flex-direction: column;background: #fff;}.screen__body {flex: 1;min-height: 0;overflow: hidden;}.screen__body--surface {overflow: auto;background: #f5f6f8;}/* ------- Top bar ------- */.topbar {display: flex;flex: 0 0 auto;min-height: 52px;align-items: center;justify-content: space-between;padding: calc(8px + env(safe-area-inset-top)) 16px 8px;border-bottom: 1px solid #e8eaed;}.topbar__title {overflow: hidden;font-size: 17px;font-weight: 600;text-overflow: ellipsis;white-space: nowrap;}.topbar__text-btn {border: 0;background: transparent;color: #1464ff;font-size: 14px;font-weight: 500;}/* ------- Tab bar ------- */.tabbar {display: flex;flex: 0 0 auto;gap: 8px;padding: 6px 12px calc(6px + env(safe-area-inset-bottom));border-top: 1px solid #e8eaed;}.tabbar__item {flex: 1;min-height: 40px;border: 0;border-radius: 12px;background: transparent;color: #6b7280;font-size: 14px;font-weight: 600;}.tabbar__item--active {background: #eef4ff;color: #1464ff;}/* ------- Chat pane ------- ** The library's <Chat> root is already `display:flex; flex-direction:column;* height:100%`, so it fills the screen on its own. The demo only arranges the* three slotted children: header (fixed) / message list (grow + scroll) /* input (fixed). `min-height:0` on the list breaks the flexbox min-height* chain so it scrolls instead of stretching the pane.*/.chat-pane__header {flex: 0 0 auto;padding-top: env(safe-area-inset-top);border-bottom: 1px solid #e8eaed;}.chat-pane > :nth-child(2) {flex: 1;min-height: 0;}.chat-pane > :last-child {flex: 0 0 auto;padding-bottom: env(safe-area-inset-bottom);border-top: 1px solid #e8eaed;}.chat-pane__actions {display: flex;align-items: center;gap: 4px;}.icon-btn {display: inline-flex;width: 36px;height: 36px;align-items: center;justify-content: center;border: 0;border-radius: 10px;background: transparent;color: #1f2329;}.icon-btn:active {background: #eef0f3;}
// Translations for this demo's OWN strings (the chat components are translated// by the UIKit itself via the `language` prop). Keys are namespaced under// `demo.` so they never collide with the library's i18next resources.//// `languageResources` is passed to <UIKitProvider>; read the strings anywhere// with `const { t } = useUIKit()` and `t('demo.chats')`.// Mirror of the library's ILanguageResource (not re-exported publicly).type TranslationTree = { [key: string]: string | TranslationTree };interface LanguageResource {lng: string;translation: TranslationTree;}const en = {demo: {appTitle: 'Chat UIKit H5',field: { sdkAppId: 'SDKAppID', userId: 'UserID', userSig: 'UserSig' },placeholder: {sdkAppId: 'Your SDKAppID',userId: 'e.g. user_001',userSig: 'Test UserSig from IM console',},error: {required: 'Please fill in SDKAppID, UserID and UserSig.',loginFailed: 'Login failed, please check your credentials.',},login: 'Login',loggingIn: 'Logging in…',chats: 'Chats',contacts: 'Contacts',logout: 'Logout',search: 'Search',setting: 'Setting',},};const kr = {demo: {appTitle: 'Chat UIKit H5',field: { sdkAppId: 'SDKAppID', userId: '사용자 ID', userSig: '사용자 서명' },placeholder: {sdkAppId: 'SDKAppID를 입력하세요',userId: '예: user_001',userSig: '콘솔에서 발급한 테스트 UserSig',},error: {required: 'SDKAppID, 사용자 ID, 사용자 서명을 입력하세요.',loginFailed: '로그인 실패, 로그인 정보를 확인하세요.',},login: '로그인',loggingIn: '로그인 중…',chats: '채팅',contacts: '연락처',logout: '로그아웃',search: '검색',setting: '설정',},};export const languageResources: LanguageResource[] = [{ lng: 'en-US', translation: en },{ lng: 'ko-KR', translation: kr },];
Parameter | Type | Note |
userID | String | Unique identifier of the user, defined by you, it is allowed to contain only upper and lower case letters (a-z, A-Z), numbers (0-9), underscores, and hyphens. |
SDKAppID | Number | |
SDKSecretKey | String | |
userSig | String | A security protection signature used for user login authentication to confirm the user's identity and prevent malicious attackers from stealing your cloud service usage rights. |
genTestUserSig (Refer to Step 3.2) function in the debug file to generate a 'userSig'. In this method, SDKSecretKey is vulnerable to decompilation and reverse engineering. Once your key is leaked, attackers can steal your Tencent Cloud traffic.


npm run dev
@trtc/calls-uikit-react dependencynpm install @trtc/calls-uikit-react
TUICallKit from @trtc/calls-uikit-react and mount it to a DOM node.src/App.tsx:import { useState } from 'react';import { LoginStatus, UIKitProvider, useLoginState } from '@tencentcloud/chat-uikit-react';import { TUICallKit } from '@trtc/calls-uikit-react';import { LoginView } from './LoginView';import { ChatLayout } from './ChatLayout';import { languageResources } from './i18n';function App() {const { status } = useLoginState();const isLogined = status === LoginStatus.SUCCESS;// `language` controls both UIKit strings and demo strings.const [language, setLanguage] = useState('en-US');return (<UIKitProvider theme="light" language={language} languageResources={languageResources}>{isLogined? (<><ChatLayout onChangeLanguage={setLanguage} />{/* Audio/video calls. Mount once at the app root; it shares the chatlogin session, so no extra login is needed. The call buttons arealready built into the chat header. */}<div className="call-kit"><TUICallKit /></div></>): <LoginView language={language} onChangeLanguage={setLanguage} />}</UIKitProvider>);}export default App;
ChatHeader property ChatHeaderRight and related code for the conversation search sidebar<ChatHeaderH5className="chat-pane__header"enableUserStatusonBack={handleChatBack}ChatHeaderRight={(<div className="chat-pane__actions">{/* <button className="icon-btn" type="button" aria-label={t('demo.search')} onClick={() => openFromChat('search')}><IconSearch size="20px" /></button> */}<button className="icon-btn" type="button" aria-label={t('demo.setting')} onClick={() => openFromChat('setting')}><IconBulletpoint size="20px" /></button></div>)}/>
ConversationList component<ConversationListH5 enableSearch={false} enableCreate={false} />
ChatSettingH5 as a full-screen drawer component. However, internal components within core components cannot be modified at this time.import { Drawer } from 'antd';const [isChatSettingShow, setIsChatSettingShow] = useState(false);function onChatSettingClose() {setIsChatSettingShow(false);}<Drawertitle="Settings"open={isChatSettingShow}onClose={onChatSettingClose}><ChatSettingH5 /></Drawer>

Was this page helpful?
You can also Contact sales or Submit a Ticket for help.
Help us improve! Rate your documentation experience in 5 mins.
Feedback