Skip to content
SLICES / TECHNICAL FOLIO Nº 001FULL-STACK ASSEMBLIES — OPEN SOURCE

Headless / Realtime

Video meeting

Build video calls & devices with the room's shared state and capability-aware controls.

48 public parts · 1examples

@slices.sh/realtime/headlessView as Markdown ↗

Your design. Our behavior.

Explore the tree to see what each part controls. Gates reveal the relevant state; roots outline their scope.

Behavior / inputGateGenericRenderer · not headlessScope
COMPONENT ANATOMYHover · focus · tap
ONE POSSIBLE DESIGNStatic preview

Room.Root · Scope

Room.RootThe room everything under here is in.

Installation

Choose what your component needs. Run these commands in an initialized Next.js app.

0

Before you start

The steps below run in a Next.js app with shadcn set up (it has a components.json). In an existing Next.js app, such as one from create-next-app, set shadcn up first:

npx shadcn@latest init -t next -b base -p nova

Or start fresh: the slices CLI creates the app and sets it up.

npx slices.sh install
1

Install the service registry

Adds services/index.ts, which import "@/services" loads. A provider setup writes the same file, so skip this if your app has one.

npx add services
2

Install the headless library

npm

Add the React parts to your app, then compose them with your own elements.

npm install @slices.sh/realtime@^0.1.6

Import from @slices.sh/realtime/headless.

3

Install the service

Connect the parts to a provider. Skip this step if this service is already configured in your app.

Provider
npx add realtime.p2p-mesh

Adds service configuration and API routes to your app, with the provider dependencies. For a headless composition, add import "@/services" after "use client".

The slices CLI installs the service and provisions its required resources in one command.

Provider setup notes

    Start small

    Parts bring the behavior. Use their default elements, or supply your own with render.

    example.tsx
    "use client"import "@/services"import { Room } from "@slices.sh/realtime/headless"export function Example() {  return (    <Room.Root room="studio">      <Room.Meeting>        <Room.Stage>          <Room.Tiles>            <Room.Tile>              <Room.TileVideo />              <Room.TileAudio />              <Room.PeerName />            </Room.Tile>          </Room.Tiles>        </Room.Stage>        <Room.MediaToggle kind="audio">Microphone</Room.MediaToggle>        <Room.MediaToggle kind="video">Camera</Room.MediaToggle>        <Room.MediaDenied>          Allow access to your camera and microphone.        </Room.MediaDenied>      </Room.Meeting>    </Room.Root>  )}

    Configure realtime. Media, captions and moderation controls hide when the provider does not grant their capability.

    API reference

    Every public part used by this experience is listed below. Gates render children without adding an element. Element parts accept native element props and render; function children receive the state declared in their props. Scopes provide context rather than a visual element.

    Room.Meetingscope

    A call's way of looking at the room: the layout this person chose, the tiles they pinned, and who has the floor. Local to this browser — nobody's layout moves anybody else's. Around the stage and the controls both, so a layout menu in a top bar and the stage it changes read the one set of choices.

    Props

    2 documented props

    Room.Meeting props
    PropTypeDefault
    pinLimit

    How many tiles may be pinned at once; the oldest pin gives way past it.

    number3
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Also inherits Partial<Pick<LayoutChoices, "mode" | "maxTiles" | "selfView" | "hideNoVideo">>. See TypeScript for the complete declaration.

    Source: @slices.sh/realtime/src/headless/meeting.tsx

    Room.CallFramecontent · <div>

    Meeting is this browser's view of a call: its layout, its pins, who has the floor. CallFrame is what full screen fills and where Meet's shortcuts listen. Stage measures the box, Tiles repeats tiles and Tile is one, with TrackIs kind="camera" | "screen" | "board". Pin and Pinned pin a tile. Overflow is the +N tile. LayoutMode, CurrentLayout, TileLimit, HideNoVideo and SelfView set the layout. FullScreen and PictureInPicture take the call elsewhere; ShortcutsSheet and ShowShortcuts list the keys.

    Required scope

    Room.CallFrame must be inside <Room.Root>.

    Props

    3 documented props · Native <div> attributes supported

    Room.CallFrame props
    PropTypeDefault
    spaceToTalk

    Hold Space to talk, anywhere in the call but a field or a button — a voice room's key.

    booleanfalse
    children

    Content rendered inside this part.

    ReactNode | ((state: { fullScreen: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/call-frame.tsx

    Room.SidePanelsscope

    Panels beside a call — one open at a time, chosen by each person for themselves. Inside a `Room.CallFrame`, ⌃⌘C opens the chat, ⌃⌘P the people, ⌃⌘B the whiteboard and ⌃⌘T the transcript, where the call has them and draws their `Room.SidePanel`.

    Props

    3 documented props

    Room.SidePanels props
    PropTypeDefault
    panelsRequired

    The panels this call has: what its toggles and shortcuts may open.

    readonly string[]—
    initial
    string | nullnull
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/side-panels.tsx

    Room.PanelAreacontent · <div>

    What holds the call and its panel: `data-state` names the open panel, for the layout to follow.

    Required scope

    Room.PanelArea must be inside <Room.SidePanels>.

    Props

    2 documented props · Native <div> attributes supported

    Room.PanelArea props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { open: string | null }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/panel-area.tsx

    Room.Stagecontent · <div>

    The call's stage: one measured box every tile is laid out in. Tiles are one flat list of tracks positioned inside it, with rects from a pure layout function — so a camera moving from the grid to the strip, or a screen taking the stage, is the same element gliding, and a playing `<video>` is never remounted. `data-shape` says what the layout came out as (`solo`, `one-on-one`, `grid`, `focus`, `spotlight`). Whoever the room's follow is on takes the stage like a pinned tile, ahead of the pins. → hooks/realtime/track-layout.ts

    Required scope

    Room.Stage must be inside <Room.Meeting>.

    Room.Stage must be inside <Room.Root>.

    Props

    4 documented props · Native <div> attributes supported

    Room.Stage props
    PropTypeDefault
    inset

    Room kept free inside the edges, in pixels, so a tile's ring is never clipped.

    number0
    insetBottom

    Room kept free at the bottom, for captions or a control bar laid over the stage.

    number0
    children

    Content rendered inside this part.

    ReactNode | ((state: { shape: string }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/stage.tsx

    Room.Tilesscope

    Everything on the stage, one scope per track: a camera per person, and a screen for each person sharing one. Keyed by track and never regrouped, so no tile changes parent when the layout does.

    Required scope

    Room.Tiles must be inside <Room.Stage>.

    Room.Tiles must be inside <Room.Root>.

    Props

    2 documented props

    Room.Tiles props
    PropTypeDefault
    group

    What the tiles are grouped in: a Motion presence group lets somebody leave, not vanish.

    ComponentType<{ children?: ReactNode }>—
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/tiles.tsx

    Room.Tilecontent · <div>

    One track on the stage — a face, a shared screen — at the rect the layout gave it. Absolutely placed inside `Room.Stage` and moved on the shared layout spring whenever the layout changes, so a tile glides from the grid to the strip and its `<video>` keeps playing: a tile never changes parent. `data-kind` is what it shows, `data-slot` where it sits (`grid`, `stage`, `strip`, `float`, `hidden`), `data-pinned` whether you pinned it and `data-followed` whether its person is the one you follow, for a skin to outline. A hidden tile is inert and out of the accessibility tree; it still exists, so a camera coming back into view does not have to start again. `data-active` says the person is reachable — a connection is open — rather than only listed. A camera tile carries its person's voice as `--level`, for a ring drawn in CSS. Enter or a double click pins it. The floating self-view can be dragged to any corner.

    Required scope

    Room.Tile must be inside <Room.Tiles>.

    Room.Tile must be inside <Room.Stage>.

    Room.Tile must be inside <Room.Meeting>.

    Props

    2 documented props · Native <div> attributes supported

    Room.Tile props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: TileState) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/tile.tsx

    Room.TrackIsgate

    The tile in scope shows this kind of track: a camera, a screen, the board.

    Required scope

    Room.TrackIs must be inside <Room.Tiles>.

    Props

    2 documented props

    Room.TrackIs props
    PropTypeDefault
    kindRequired
    "camera" | "screen" | "board"—
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/track-is.tsx

    Room.TileScreencontent · <video>

    What one peer is sharing. Its own part rather than a second use of `Room.TileVideo`, because a camera and a screen are two pictures and an element plays the first video track it is handed: one element for both is one of them showing and the other silently lost. Rendered only while that peer says it is sharing. → docs/rules/capability-honesty.md Silent, like every tile: a shared screen's sound, where a browser gives any, is that peer's audio and belongs to `Room.TileAudio`.

    Required scope

    Room.TileScreen must be inside <Room.Peers>.

    Props

    2 documented props · Native <video> attributes supported

    Room.TileScreen props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/tile-screen.tsx

    Room.TileVideocontent · <video>

    One track's picture: a peer's camera on a camera tile, the screen they share on a screen tile. Outside a stage, where a tile is a person, it is their camera. Rendered only while that peer says the track is on, because a `<video>` with no track is a black rectangle and a grid of them looks like an outage. What the peer *says* decides, not whether a track happens to exist. → docs/rules/capability-honesty.md Silent, always: the voice is `Room.TileAudio`'s job, so an unmuted tile would play it twice, and your own unmuted tile is your microphone in your own speakers. `data-state="mine"` on your own camera, which a skin flips: a self-view is a mirror. A screen is never flipped, yours included — a mirrored slide is a slide nobody can read.

    Required scope

    Room.TileVideo must be inside <Room.Peers> or <Room.Tiles>.

    Props

    2 documented props · Native <video> attributes supported

    Room.TileVideo props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/tile-video.tsx

    Room.TileAudiocontent · <audio>

    One peer's sound. Never your own: playing your own microphone back is a howl, and it is the first thing anybody building a room gets wrong.

    Required scope

    Room.TileAudio must be inside <Room.Peers>.

    Props

    2 documented props · Native <audio> attributes supported

    Room.TileAudio props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/tile-audio.tsx

    Room.CameraOffgate

    This peer has no picture on screen: where the initials go instead of one. A camera somebody said was off and a camera nobody has heard about are the same thing to look at — there is no picture either way — so `null` belongs here with `false`. It is the badges that must not guess. → docs/rules/capability-honesty.md

    Required scope

    Room.CameraOff must be inside <Room.Peers>.

    Props

    1 documented props

    Room.CameraOff props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/camera-off.tsx

    Room.Mutedgate

    This peer says its microphone is off. What it says, not what its audio sounds like: silence is not mute, and a tile that guessed would label every pause. → docs/rules/capability-honesty.md And said, rather than merely not said: `micOn` is `null` for the second before a peer's first control message and for as long as nobody can reach them, and a badge there was a claim nobody had made — every new face arrived wearing a crossed-out microphone.

    Required scope

    Room.Muted must be inside <Room.Peers>.

    Props

    1 documented props

    Room.Muted props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/muted.tsx

    Room.Speakinggate

    This peer is making a noise a person would call talking. Measured, not announced: a peer cannot tell you it is speaking any faster than it can send you the sound. From the level the page already reads for rings, on one shared analyser, or from the transport where it knows. → ./use-talking.ts

    Required scope

    Room.Speaking must be inside <Room.Peers>.

    Props

    1 documented props

    Room.Speaking props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/speaking.tsx

    Room.LinkQualitycontent · <span>

    A badge for somebody whose connection is struggling: the answer to "did they freeze, or did I?". Nothing while their link is fine, and nothing before it has been measured or where it cannot be — a badge that always shows teaches everybody to stop reading it. `data-state` is `fair` or `poor`; its children get the same, for words that say which.

    Required scope

    Room.LinkQuality must be inside <Room.Peers>.

    Props

    2 documented props · Native <span> attributes supported

    Room.LinkQuality props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { quality: Quality }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/link-quality.tsx

    Room.Pinaction · <button>

    Pin this tile for yourself, or unpin it. Pinned tiles take the stage, oldest first, and nobody else's view moves. `aria-pressed` and `data-active` while pinned.

    Required scope

    Room.Pin must be inside <Room.Tiles>.

    Room.Pin must be inside <Room.Meeting>.

    Props

    2 documented props · Native <button> attributes supported

    Room.Pin props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { pinned: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/pin.tsx

    Room.Pinnedgate

    You pinned the tile in scope.

    Required scope

    Room.Pinned must be inside <Room.Tiles>.

    Room.Pinned must be inside <Room.Meeting>.

    Props

    1 documented props

    Room.Pinned props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/pinned.tsx

    Room.HandRaisedgate

    This peer has a hand up, and where they are in the queue of raised hands, from 1.

    Required scope

    Room.HandRaised must be inside <Room.Peers> or <Room.Tiles>.

    Room.HandRaised must be inside <Room.Root>.

    Props

    2 documented props

    Room.HandRaised props
    PropTypeDefault
    group
    GateGroupComponent—
    children

    Content rendered inside this part.

    ReactNode | ((state: { place: number }) => ReactNode)—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/hand-raised.tsx

    Room.TileReactiongate

    The emoji this peer sent last, while it is still in the air: a badge on their tile. Each emoji is an arrival of its own, so a second one replaces the first rather than relabelling it.

    Required scope

    Room.TileReaction must be inside <Room.Tiles>.

    Room.TileReaction must be inside <Room.Root>.

    Props

    2 documented props

    Room.TileReaction props
    PropTypeDefault
    group
    GateGroupComponent—
    children

    Content rendered inside this part.

    ReactNode | ((state: { emoji: string; id: string }) => ReactNode)—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/tile-reaction.tsx

    Room.Overflowcontent · <div>

    The last cell of a full stage: how many are not shown, and who. Nothing while everybody fits. Placed and moved like a tile, on the same spring. Drawn even for a mesh, which stops long before it needs one, so a transport that carries more people later needs no new screen.

    Required scope

    Room.Overflow must be inside <Room.Stage>.

    Room.Overflow must be inside <Room.Root>.

    Props

    2 documented props · Native <div> attributes supported

    Room.Overflow props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: OverflowState) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/overflow.tsx

    Room.SidePanelcontent · <aside>

    One panel beside the call, drawn only while it is the open one. Escape closes it, from anywhere inside it — the way out of a panel a person opened by accident. While mounted it is what makes its panel reachable: inside a gate that hides it, its toggle and shortcut do nothing.

    Required scope

    Room.SidePanel must be inside <Room.SidePanels>.

    Props

    3 documented props · Native <aside> attributes supported

    Room.SidePanel props
    PropTypeDefault
    panelRequired
    string—
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/side-panel.tsx

    Room.PanelToggleaction · <button>

    Opens one panel beside the call, or closes it. `aria-expanded` and `data-active` while open.

    Required scope

    Room.PanelToggle must be inside <Room.SidePanels>.

    Props

    3 documented props · Native <button> attributes supported

    Room.PanelToggle props
    PropTypeDefault
    panelRequired
    string—
    children

    Content rendered inside this part.

    ReactNode | ((state: { open: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/panel-toggle.tsx

    Room.LocalVideocontent · <video>

    Your own picture. Muted, always: an unmuted local tile is your microphone coming out of your own speakers.

    Required scope

    Room.LocalVideo must be inside <Room.Root>.

    Props

    2 documented props · Native <video> attributes supported

    Room.LocalVideo props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/local-video.tsx

    Room.LayoutModeaction · <button>

    Choose how the stage is laid out. `aria-pressed` and `data-active` on the current one.

    Required scope

    Room.LayoutMode must be inside <Room.Meeting>.

    Props

    3 documented props · Native <button> attributes supported

    Room.LayoutMode props
    PropTypeDefault
    modeRequired
    "auto" | "tiled" | "spotlight" | "sidebar"—
    children

    Content rendered inside this part.

    ReactNode | ((state: { active: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/layout-mode.tsx

    Room.CurrentLayoutcontent · <span>

    The layout in use, for a menu's trigger to name. `data-state` is the mode.

    Required scope

    Room.CurrentLayout must be inside <Room.Meeting>.

    Props

    2 documented props · Native <span> attributes supported

    Room.CurrentLayout props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { mode: LayoutMode }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/current-layout.tsx

    Room.TileLimitinput · <input>

    How many tiles the stage shows before the rest are counted on a "+N" tile.

    Required scope

    Room.TileLimit must be inside <Room.Meeting>.

    Props

    2 documented props · Native <input> attributes supported

    Room.TileLimit props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { value: number }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/tile-limit.tsx

    Room.HideNoVideoaction · <button>

    Leave out the tiles of people whose camera is off, unless they are pinned. A switch.

    Required scope

    Room.HideNoVideo must be inside <Room.Meeting>.

    Props

    2 documented props · Native <button> attributes supported

    Room.HideNoVideo props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { on: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/hide-no-video.tsx

    Room.SelfViewaction · <button>

    Where your own picture goes: chosen for you, in the grid, floating, or nowhere.

    Required scope

    Room.SelfView must be inside <Room.Meeting>.

    Props

    3 documented props · Native <button> attributes supported

    Room.SelfView props
    PropTypeDefault
    viewRequired
    "auto" | "tile" | "float" | "hidden"—
    children

    Content rendered inside this part.

    ReactNode | ((state: { active: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/self-view.tsx

    Room.RaiseHandaction · <button>

    Put your hand up, or down. Everybody sees it, in the order hands went up. `aria-pressed` and `data-active` while it is up.

    Required scope

    Room.RaiseHand must be inside <Room.Root>.

    Props

    2 documented props · Native <button> attributes supported

    Room.RaiseHand props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { raised: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/raise-hand.tsx

    Room.Handsscope

    The queue of raised hands, first raised first, each in its peer's scope.

    Required scope

    Room.Hands must be inside <Room.Root>.

    Props

    1 documented props

    Room.Hands props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/hands.tsx

    Room.Reactaction · <button>

    Send an emoji to the room: it rises from the edge for everybody, with your name.

    Required scope

    Room.React must be inside <Room.Root>.

    Props

    3 documented props · Native <button> attributes supported

    Room.React props
    PropTypeDefault
    emojiRequired
    string—
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/react.tsx

    Room.Flightsscope

    Emoji in the air, each in its own scope with its lane and its sender. A sender keeps one lane while their emoji are flying, so two people's names never land on each other, and only the first emoji of a burst carries the name. → hooks/realtime/use-reactions.ts

    Required scope

    Room.Flights must be inside <Room.Root>.

    Props

    2 documented props

    Room.Flights props
    PropTypeDefault
    group

    What the flights are grouped in: a Motion presence group lets each one finish rising.

    ComponentType<{ children?: ReactNode }>—
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/flights.tsx

    Room.Flightcontent · <div>

    One emoji rising. `--lane` is its lane from 0, for a skin to place it; `named` says whether it is the first of its sender's burst, which is the one that carries the name.

    Required scope

    Room.Flight must be inside <Room.Flights>.

    Props

    2 documented props · Native <div> attributes supported

    Room.Flight props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: FlightRecord) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/flight.tsx

    Room.DeviceGroupcontent · <div>

    The devices of one kind as a choice of one, for a menu's radio group: its `value` is the device in use and choosing another switches to it, or remembers it for when the camera or microphone is next turned on. Items are `Room.Devices` with `Room.DeviceItem` inside.

    Required scope

    Room.DeviceGroup must be inside <Room.Root>.

    Props

    3 documented props · Native <div> attributes supported

    Room.DeviceGroup props
    PropTypeDefault
    kindRequired
    "audioinput" | "videoinput"—
    children

    Content rendered inside this part.

    ReactNode | ((state: { chosen: string | null }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/device-group.tsx

    Room.NoDevicesgate

    The browser admits to no device of this kind yet — usually because nobody has been asked for permission, which is when a list is empty rather than when there is nothing plugged in.

    Required scope

    Room.NoDevices must be inside <Room.DeviceGroup>.

    Props

    1 documented props

    Room.NoDevices props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/no-devices.tsx

    Room.Devicesscope

    Every device the picker around this is offering, each in its own scope.

    Required scope

    Room.Devices must be inside <Room.DevicePicker>.

    Props

    1 documented props

    Room.Devices props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/devices.tsx

    Room.DeviceItemcontent · <div>

    One device in a `Room.DeviceGroup`, named as the browser names it.

    Required scope

    Room.DeviceItem must be inside <Room.Devices>.

    Props

    2 documented props · Native <div> attributes supported

    Room.DeviceItem props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/device-item.tsx

    Room.DevicePickerinput · <select>

    Which microphone or camera to use. Choosing one while it is off remembers the choice, and the device is opened when the toggle beside it is turned on — so picking from a list never switches a light on by itself. The remembering is `useLocalMedia`'s, because that is what owns `set`. A list of one is not a choice, and a list of none is a browser that has not been asked for permission yet — neither is worth a control, so neither draws one.

    Required scope

    Room.DevicePicker must be inside <Room.Root>.

    Props

    3 documented props · Native <select> attributes supported

    Room.DevicePicker props
    PropTypeDefault
    kindRequired
    "audioinput" | "videoinput"—
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/device-picker.tsx

    Room.DeviceOptioncontent · <option>

    One device in the list. The label is empty until permission has been given once — that is the browser keeping the list from being a fingerprint, not a bug — so before permission this reads "Microphone 1" and after it reads the real name. → hooks/realtime/use-devices.ts

    Required scope

    Room.DeviceOption must be inside <Room.Devices>.

    Props

    2 documented props · Native <option> attributes supported

    Room.DeviceOption props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/device-option.tsx

    Room.ChosenDevicecontent · <span>

    The name of the camera or microphone in use, for a menu's trigger: what a person checks before a call, and cannot see on a button that only says "Microphone". Its children stand in until the browser names a device, which it does only once one was allowed.

    Required scope

    Room.ChosenDevice must be inside <Room.Root> or <Join.Root>.

    Props

    3 documented props · Native <span> attributes supported

    Room.ChosenDevice props
    PropTypeDefault
    kindRequired
    "audioinput" | "videoinput"—
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/chosen-device.tsx

    Room.MyLevelcontent · <span>

    Your own microphone's level as `--level`, for a meter beside the microphone button: the one way to know a microphone is working before anybody has to ask whether you can be heard.

    Required scope

    Room.MyLevel must be inside <Room.Root>.

    Props

    2 documented props · Native <span> attributes supported

    Room.MyLevel props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/my-level.tsx

    Room.MediaToggleaction · <button>

    Turns one kind of track on or off. `data-active` while it is on. Disabled while there is no room to send into, because there is nothing for a camera to reach and the light would come on anyway: a control bar that still worked over a room that was full or refused was the loudest thing wrong with a meeting that had failed. → docs/rules/capability-honesty.md

    Required scope

    Room.MediaToggle must be inside <Room.Root>.

    Props

    3 documented props · Native <button> attributes supported

    Room.MediaToggle props
    PropTypeDefault
    kindRequired
    "audio" | "video" | "screen"—
    children

    Content rendered inside this part.

    ReactNode | ((state: { on: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/media-toggle.tsx

    Room.MediaDeniedgate

    The browser refused the camera or the microphone. `unsupported` is told apart from `denied` because the remedies are different and only one of them exists: a permission can be changed in the address bar, and a browser without WebRTC cannot be talked round. → docs/rules/capability-honesty.md

    Required scope

    Room.MediaDenied must be inside <Room.Root>.

    Props

    1 documented props

    Room.MediaDenied props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { unsupported: boolean }) => ReactNode)—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/media-denied.tsx

    Room.MutedNudgegate

    You are talking and nobody can hear you: where a call says so, with a way to unmute. Rendering it is what starts the listening — on this device only, and only where the microphone was already allowed. → hooks/realtime/use-muted-nudge.ts

    Required scope

    Room.MutedNudge must be inside <Room.Root>.

    Props

    2 documented props

    Room.MutedNudge props
    PropTypeDefault
    group
    GateGroupComponent—
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/muted-nudge.tsx

    Room.FullScreenaction · <button>

    Fill the screen with the call, or leave full screen. Nothing where the browser cannot — an iPhone's Safari, a frame without permission — rather than a button that does nothing.

    Required scope

    Room.FullScreen must be inside <Room.CallFrame>.

    Props

    2 documented props · Native <button> attributes supported

    Room.FullScreen props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { on: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/full-screen.tsx

    Room.PictureInPictureaction · <button>

    Floats the video worth watching — a shared screen, then whoever is speaking — over other windows, and brings it back. `aria-pressed` while it floats; nothing in a browser that cannot.

    Props

    2 documented props · Native <button> attributes supported

    Room.PictureInPicture props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { on: boolean }) => ReactNode)—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/picture-in-picture.tsx

    Room.ShowShortcutsaction · <button>

    Open the sheet of keyboard shortcuts, for somebody who did not know to press `?`.

    Required scope

    Room.ShowShortcuts must be inside <Room.CallFrame>.

    Props

    2 documented props · Native <button> attributes supported

    Room.ShowShortcuts props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/show-shortcuts.tsx

    Room.ShortcutsSheetcontent

    The sheet that lists the call's keys, as a dialog root: `open` and `onOpenChange` are handed to whatever it renders, so a skin's dialog opens on `?` and closes on Escape.

    Required scope

    Room.ShortcutsSheet must be inside <Room.CallFrame>.

    Props

    2 documented props

    Room.ShortcutsSheet props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode—
    render

    Replace the default element with your own while retaining the part’s behavior and accessibility.

    ReactElement—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/shortcuts-sheet.tsx

    Room.Rootscope

    The room everything under here is in. Joining happens in the provider's effect rather than here, so a component that renders twice before it commits does not put two peers in the roster for one tab. A root inside a root for the same room shares its connection, and its follow too: a presence list placed on a canvas has to move the canvas when somebody on it is picked, and two follows would be two lists of whom somebody is following. A root inside a root for the same room needs no service of its own, and says nothing when there is none. Optional with no service, or off, there is no room: its status is "off", so every gate stays shut and `Room.Present` hides what the room is drawn inside, and its sink is switched off, so a Composer inside renders nothing. → ../hooks/use-realtime-scope.ts

    Props

    8 documented props

    Room.Root props
    PropTypeDefault
    config

    The realtime config, a slug from the registry (services/realtime/<config>.config.ts). The nearest root's, else "default", when left out.

    string—
    enabled

    Left out or true: required, and a missing service throws. "optional": renders its children, its parts render nothing, and its sink is switched off. false: off. A capability name ("relay") requires the service and that capability.

    Enabled—
    roomRequired
    string—
    profile

    Who this browser shows itself as. Without one, a visitor gets an animal of their own; and somebody signed in is their session's name and picture whatever this says.

    PeerProfile—
    perPerson

    One person across this browser's tabs: every tab takes the same name, the one a screen that counts people shows once. Off, each tab is somebody of its own.

    boolean—
    media

    Off, the transport never asks for a camera or a microphone.

    boolean—
    captions

    Captions start shown; each person can still hide them for themselves.

    boolean—
    children

    Content rendered inside this part.

    ReactNode—

    Required props are marked. A dash means no explicit default is declared in this component.

    Source: @slices.sh/realtime/src/headless/room-root.tsx

    When something is missing

    No service configuration
    Install the realtime setup below and import "@/services". Required service roots report missing setup; gates do not replace that configuration.
    Missing capability or parent scope
    Follow each part’s context requirements and the example composition. Capability-aware controls hide when the provider cannot perform their action.
    Read the setup guide →