LevelChatLevelChatDocs
VideoTile

Components

VideoTile

One participant, glass chrome, signature treatment.

VideoTile is the signature surface of the kit. It renders one participant's video (or avatar placeholder) inside the glass tile chrome that defines the LevelChat look.

Install

pnpm add @levelchat/react-components @levelchat/web

Requirements

  • Reactreact and react-dom ^18 || ^19 (peer dependency).
  • LevelChat Web SDK@levelchat/web ^0.2.4 (peer dependency, wires hook-driven mode to the live Room).
  • Provider — wrap the tree in <LevelChatProvider> from @levelchat/web-react so hook-driven mode can subscribe to room events. Prop-driven demos work without it.
  • Styles — import the brand tokens + kit stylesheet exactly once at the app entry, before any kit component renders.

In Next.js (App Router), import the stylesheets at the top of your root layout:

TypeScript (TSX)
// app/layout.tsx
import '@levelchat/brand/tokens.css';
import '@levelchat/react-components/styles.css';

In Vite (or any plain React app), import them at the top of your entry module:

TypeScript (TSX)
// src/main.tsx
import '@levelchat/brand/tokens.css';
import '@levelchat/react-components/styles.css';

Basic usage

Drop the component inside a <LevelChatProvider> and pass the participant id. The tile subscribes to live track events automatically:

import { VideoTile } from '@levelchat/react-components';
import { LevelChatProvider } from '@levelchat/web-react';

export function Stage({ token, participantId }) {
  return (
    <LevelChatProvider autoJoin={{ token }}>
      <VideoTile participantId={participantId} source="camera" />
    </LevelChatProvider>
  );
}

For static demos or Storybook, drive the tile with props instead of a hook:

Headless hook

The styled component delegates all its room state to useVideoTrack. Reach for it when you want a fully custom render:

import { useVideoTrack } from '@levelchat/react-components';

function MyCustomTile({ participantId }) {
  const { stream, isLocal, isPaused, networkScore, displayName } = useVideoTrack({
    participantId,
    source: 'camera',
  });
  // ...render your own surface
}

Slot composition

Every styled component exposes a slot map for Radix-style headless composition. Use the slots when you need a custom layout:

<VideoTile.Container aspect="16:9" cornerRadius={20}>
  <VideoTile.Video stream={stream} mirror />
  <VideoTile.Overlay>
    <YourCaptions />
  </VideoTile.Overlay>
  <VideoTile.Name name="Mira Saito" />
  <VideoTile.MicIndicator muted={false} />
  <VideoTile.NetworkIndicator quality="good" asDot />
</VideoTile.Container>

Theming

The tile reads every visual token from CSS variables:

  • --lc-radius-tile — corner radius (defaults to 14 px). Pass cornerRadius as a prop to override per instance.
  • --lc-border-subtle — hairline border on the tile.
  • --lc-brand-primary — speaking ring + network "good" dot.
  • --lc-warning-solid, --lc-danger-solid — network "fair" / "poor", plus the muted mic chip.
  • --lc-shadow-tile, --lc-shadow-glass — tile + chip shadows.

Override any token in your global CSS to retheme without touching component code:

:root {
  --lc-radius-tile: 20px;
  --lc-brand-primary: #ff3366;
  --lc-shadow-tile: 0 4px 12px rgba(0,0,0,.1);
}

Props

PropTypeDefaultNotes
participantIdstringHook-driven mode. Required for live wiring.
source'camera' | 'screen'cameraWhich video source to render.
namestringOverride the displayed name.
connection'good' | 'fair' | 'poor'hookForce the network indicator.
mutedbooleanhookForce the mic indicator.
speakingbooleanfalseShow the speaking ring.
aspect'16:9' | '4:3' | '1:1'16:9Aspect ratio.
cornerRadiusnumber | stringvar(--lc-radius-tile)Per-instance corner override.
backgroundstringautoCSS background for no-video state (demos).
livebooleanfalseShow the LIVE pip.
animate'none' | 'enter'noneMount animation.