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.
Room.Root · Scope
Installation
Choose what your component needs. Run these commands in an initialized Next.js app.
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 novaOr start fresh: the slices CLI creates the app and sets it up.
npx slices.sh installInstall 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 servicesInstall the headless library
npmAdd the React parts to your app, then compose them with your own elements.
npm install @slices.sh/realtime@^0.1.6Import from @slices.sh/realtime/headless.
Install the service
Connect the parts to a provider. Skip this step if this service is already configured in your app.
npx add realtime.p2p-meshAdds 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.
"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
| Prop | Type | Default |
|---|---|---|
sideRequired | "stage" | "audience" | — |
childrenContent rendered inside this part. | ReactNode | ((state: { count: number }) => ReactNode) | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
sideRequired | "stage" | "audience" | — |
everybodyEverybody on this side, whatever they claim: a room with no stage is one circle. | boolean | false |
groupWhat the seats are grouped in: a Motion presence group lets a seat arrive and leave. | ComponentType<{ children?: ReactNode }> | — |
childrenContent 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
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { side: Side }) => ReactNode) | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
childrenContent 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
| Prop | Type | Default |
|---|---|---|
group | ComponentType<{ children?: ReactNode }> | — |
childrenContent 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
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { volume: number }) => ReactNode) | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
childrenContent 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
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
childrenContent 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
| Prop | Type | Default |
|---|---|---|
childrenContent 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
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { talking: boolean }) => ReactNode) | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
childrenContent rendered inside this part. | ReactNode | ((state: { deafened: boolean }) => ReactNode) | — |
renderReplace 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
| Prop | Type | Default |
|---|---|---|
configThe realtime config, a slug from the registry ( | string | — |
enabledLeft out or | Enabled | — |
roomRequired | string | — |
profileWho 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 | — |
perPersonOne 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 | — |
mediaOff, the transport never asks for a camera or a microphone. | boolean | — |
captionsCaptions start shown; each person can still hide them for themselves. | boolean | — |
childrenContent 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.