Meet API Docs Dashboard
On this page

React UI Kit for Meet Calls

@respiresoft/meet-react, React 18 and 19, built on the headless client.

Publishing. The package is not on npm yet. For a call you can ship today use the embed SDK.

It sits on the headless client: use <MeetRoom> for a complete screen, or the hooks and components for your own layout.

Reference from the package README#

React components and hooks for Meet calls. They are built only on @respiresoft/meet-client, the headless client, so a screen made with them gets last-N and simulcast, reconnecting and host rules for free. No CSS framework: one small stylesheet, every colour, radius and font from CSS variables.

Use <MeetRoom> for a whole call screen, or the hooks and parts for your own layout. Use the embed (@respiresoft/meet-embed) when you want Meet's own page in an iframe.

Install#

text
npm install @respiresoft/meet-react @respiresoft/meet-client

React 18 or 19. ESM, with types. Your server mints the meeting token (see the server SDKs); the browser never sees an API key. These packages are not on npm yet: until they are, install from the repository folders (sdk/client, sdk/react).

The one-line room#

jsx
import { MeetRoom } from '@respiresoft/meet-react';
import '@respiresoft/meet-react/style.css';

export default function Call({ token }) {
  return <div style={{ height: '100dvh' }}><MeetRoom baseUrl="https://meet.example.com" token={token} /></div>;
}

That gives the pre-join screen (camera preview, microphone and camera choice, a name box only if the token has no name), the call (grid, controls, chat beside it, captions, reactions, a shared screen), a reconnecting banner, and an end screen. The room fills its parent, so give the parent a height. onLeave(reason) runs once when the call ends. skipPreJoin joins at once. theme is light, dark or auto; brand takes a white-label profile.

A custom layout from the hooks#

jsx
import { MeetProvider, useParticipants, useTracks, useChat, MediaView, AudioRenderer, ControlBar } from '@respiresoft/meet-react';

function Tile({ p }) {
  const { video } = useTracks(p.id);
  return <figure><MediaView track={video} /><figcaption>{p.name}{p.micEnabled ? '' : ' (muted)'}</figcaption></figure>;
}

function Screen() {
  const people = useParticipants();
  const { messages, send } = useChat();
  return (
    <>
      {people.map((p) => <Tile key={p.id} p={p} />)}
      <button onClick={() => send('hello')}>Say hello ({messages.length})</button>
      <AudioRenderer />
      <ControlBar />
    </>
  );
}

<MeetProvider baseUrl="https://meet.example.com" token={token}>
  <Screen />
</MeetProvider>

Nothing joins until client.join({ audio, video, name }) runs (useMeet().client). <MeetRoom> and <PreJoin> do it for you. Pass your own client to <MeetProvider client={...}> to keep it outside React; a client the provider makes is left when it unmounts. If you draw your own tiles, report their size so the engine sends the right video: useTileHint(ref, client, participant.id).

Hooks#

Hook Returns
useMeet() { client, state, role, canHost, permissions, recording, leftReason, error }
useParticipants() everybody in the call, you first, then the others in join order (people not in the call yet are left out)
useLocalParticipant() you
useTracks(id) { audio, video, screen, screenAudio } tracks of one person
useActiveSpeaker() { participant, level } of whoever was loudest last, or null
useChat() { messages, send(text) }
useCaptions() { enabled, setEnabled(on), lines }; lines show the translation into your caption language when there is one, and drop off after a few seconds
useDevices() { audioInput, videoInput, audioOutput, selected, select(kind, id), refresh, canChooseOutput }
useHostActions() { canHost, canRecord, recording, mute, disableCamera, lowerHand, remove, endMeeting, startRecording, stopRecording }
useReactions() emoji that arrived in the last few seconds
useTileHint(ref, client, id) reports a tile's height to setVideoQuality, null when it is off screen or gone

Components#

Component What it is
<MeetProvider client | baseUrl+token labels> gives the hooks and parts their client
<MeetRoom> pre-join, call, chat and end screen in one
<VideoGrid> tiles that fill the space, active speaker first, a shared screen large with a strip, pages of 24 (pageSize); every tile reports its size, tiles off screen or on another page report null
<ParticipantTile participant source speaking> video or initials, name, muted and hand marks, speaking ring, host actions menu
<ControlBar onToggleChat chatOpen unread onLeave> mic and camera with a device menu each, screen share, hand, reactions, chat, captions, host tools, leave; the less used buttons fold into "More" on a narrow bar; viewers get chat, captions and leave
<ChatPanel onClose> messages and the box
<CaptionsBar> the last few caption lines
<PreJoin onJoin askName busy error> preview, devices, a name only when the token has none; onJoin({ name, audio, video, devices })
<AudioRenderer> plays every remote microphone and screen sound, on the speaker chosen with selectDevices({ audioOutput }) where the browser has setSinkId
<ReactionLayer> floating emoji
<MediaView track> a <video> fed from one track

Every component takes labels for per-component strings.

Theming#

Import @respiresoft/meet-react/style.css, then set variables on .meet-root (or any parent). The names follow the white-label brand profile keys (docs/WHITELABEL.md), with _ written as -:

Variable Brand key
--meet-background, --meet-surface, --meet-border, --meet-text, --meet-muted colors.*
--meet-accent, --meet-accent-text, --meet-danger colors.accent, colors.accent_text, colors.danger
--meet-font font
--meet-radius radius

Dark colours apply with theme="dark", or on auto when the device is dark. brandVars(brand, 'light' | 'dark') turns a brand profile into these variables; <MeetRoom brand={...}> does it for you. Also: --meet-gap (8px) and --meet-touch (44px). Layout works from 360 px; touch targets are 44 px on touch screens; buttons have labels, menus close on Escape and move with the arrow keys.

Words#

English only. Pass labels (a partial object, see defaultLabels for every key) to change or translate any string, for example labels={{ leave: 'Quitter', dir: 'rtl' }}. {name}, {n}, {page} and {pages} in a label are filled in.