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

Headless / Realtime

Voice room

Build voice stage with the room's shared state and capability-aware controls.

14 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.Seats side="stage">        <Room.Seat>          <Room.PeerAvatar />          <Room.PeerName />          <Room.SeatReactions />        </Room.Seat>      </Room.Seats>      <Room.RaiseHand>Ask to speak</Room.RaiseHand>      <Room.LowerHand>Lower hand</Room.LowerHand>      <Room.PushToTalk>Hold to talk</Room.PushToTalk>    </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.SeatCountcontent · <span>

    How many are on one side of the room.

    Required scope

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

    Props

    3 documented props · Native <span> attributes supported

    Room.SeatCount props
    PropTypeDefault
    sideRequired
    "stage" | "audience"—
    children

    Content rendered inside this part.

    ReactNode | ((state: { count: 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/seat-count.tsx

    Room.Seatsscope

    The people on one side of a voice room, each in their own scope: the stage, or the audience. Keyed by person on both sides, so somebody invited up is the same seat moving across rather than one seat leaving and another arriving — which a shared layout animation draws as a face travelling to the stage.

    Required scope

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

    Props

    4 documented props

    Room.Seats props
    PropTypeDefault
    sideRequired
    "stage" | "audience"—
    everybody

    Everybody on this side, whatever they claim: a room with no stage is one circle.

    booleanfalse
    group

    What the seats are grouped in: a Motion presence group lets a seat arrive and leave.

    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/seats.tsx

    Room.Seatcontent · <li>

    One person's seat in a voice room. `data-state` is `stage` or `audience`, and the seat carries their voice as `--level`, for a ring that grows as they speak.

    Required scope

    Room.Seat must be inside <Room.Seats>.

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

    Props

    2 documented props · Native <li> attributes supported

    Room.Seat props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { side: Side }) => 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/seat.tsx

    Room.IsHostgate

    The person in scope runs the room.

    Required scope

    Room.IsHost must be inside <Room.Seats>.

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

    Props

    1 documented props

    Room.IsHost 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/is-host.tsx

    Room.SeatReactionsscope

    The emoji the person in scope sent, while they float up from their seat.

    Required scope

    Room.SeatReactions must be inside <Room.Seats>.

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

    Props

    2 documented props

    Room.SeatReactions props
    PropTypeDefault
    group
    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/seat-reactions.tsx

    Room.Volumeinput · <input>

    How loud the person in scope is for you alone, from 0 to 100. Nobody else is told.

    Required scope

    Room.Volume must be inside <Room.Seats>.

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

    Props

    2 documented props · Native <input> attributes supported

    Room.Volume props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { volume: 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/volume.tsx

    Room.Hostinggate

    You run the room: what only a host may do — invite, move, dismiss — is drawn here.

    Required scope

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

    Props

    1 documented props

    Room.Hosting 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/hosting.tsx

    Room.MoveToAudienceaction · <button>

    Ask the speaker in scope to step down into the audience. Only a host's button does anything, so only a host's is enabled.

    Required scope

    Room.MoveToAudience must be inside <Room.Seats>.

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

    Props

    2 documented props · Native <button> attributes supported

    Room.MoveToAudience 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/move-to-audience.tsx

    Room.Inviteaction · <button>

    Ask the person in scope up onto the stage. They move when their own browser says so. Only a host's button does anything, so only a host's is enabled.

    Required scope

    Room.Invite must be inside <Room.Seats>.

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

    Props

    2 documented props · Native <button> attributes supported

    Room.Invite 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/invite.tsx

    Room.LowerHandaction · <button>

    Put somebody's hand down for them, as a host answering their request. Only a host's button does anything, so only a host's is enabled.

    Required scope

    Room.LowerHand must be inside <Room.Seats>.

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

    Props

    2 documented props · Native <button> attributes supported

    Room.LowerHand 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/lower-hand.tsx

    Room.Askinggate

    Somebody in the audience is asking to speak, and how many.

    Required scope

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

    Props

    1 documented props

    Room.Asking props
    PropTypeDefault
    children

    Content rendered inside this part.

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

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

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

    Room.Requestsscope

    The audience's raised hands, first raised first: who is asking to speak, and in what order.

    Required scope

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

    Props

    1 documented props

    Room.Requests 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/requests.tsx

    Room.PushToTalkaction · <button>

    Hold to talk: the microphone is on while this is held, and off the moment it is let go — or the pointer leaves it, or the window loses focus, because a talk button stuck down is a room that hears everything. Nobody in the audience can hold it; they raise a hand instead. `data-active` while talking.

    Required scope

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

    Props

    2 documented props · Native <button> attributes supported

    Room.PushToTalk props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { talking: 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/push-to-talk.tsx

    Room.Deafenaction · <button>

    Hear nobody, or everybody again. Local: nobody is told you stopped listening.

    Required scope

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

    Props

    2 documented props · Native <button> attributes supported

    Room.Deafen props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { deafened: 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/deafen.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 →