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

Headless / Realtime

Team conversation

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

33 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 @slices.sh/composer@^0.1.4

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"import { Composer } from "@slices.sh/composer"export function Example() {  return (    <Room.Root room="studio">      <Room.Messages>        <Room.Message>          <Room.MessageAuthor />          <Room.MessageBody />        </Room.Message>      </Room.Messages>      <Composer.Root>        <Composer.Input aria-label="Message" />        <Composer.Submit>Send</Composer.Submit>      </Composer.Root>    </Room.Root>  )}

    Room.Root receives the Composer message and publishes it to the room.

    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.Transcriptcontent · <div>

    The scrolling region a conversation arrives in. A log, so a screen reader reads a message as it lands rather than never: `role="log"` is the one live region defined for exactly this, and it announces additions instead of re-reading the transcript. `data-state` is `pinned` or `scrolled`, which is what a skin shows a "jump to latest" over. The region rather than the list, and the messages keep a `<ul>` of their own inside it: a role replaces whatever the element was, so a `<ul role="log">` is a list no longer — the bubbles inside it stop being items and a screen reader reads the conversation as one run of text with no boundaries in it. (WAI-ARIA, "Presentational Children" and role conflict) A tab stop, because a region that scrolls and cannot be focused cannot be scrolled by anybody using a keyboard: the whole conversation above the fold is unreachable, which is WCAG 2.1.1 and the reason a chat log is the APG's own example of this. It needs a name, and the name is a word, so the screen gives it. (WCAG 2.4.3, 4.1.2)

    Required scope

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

    Props

    2 documented props · Native <div> attributes supported

    Room.Transcript props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { pinned: boolean; unseen: 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/transcript.tsx

    Room.NoMessagesgate

    Nothing has been said since you arrived, which for this service is the usual start.

    Required scope

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

    Props

    1 documented props

    Room.NoMessages 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-messages.tsx

    Room.Timelinescope

    The conversation in order: each turn of one person's messages, and each marker between — somebody arriving, leaving, starting to present. Each item gates itself: `Room.Run` draws a turn and `Room.Marker` a marker, so the list is listed once and branches nowhere.

    Required scope

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

    Props

    2 documented props

    Room.Timeline props
    PropTypeDefault
    group

    What the items are grouped in: a Motion presence group lets a message arrive, not appear.

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

    Room.Markercontent · <li>

    Something that happened between messages: somebody arrived, left or started presenting. `data-state` is the event. The words are the pattern's, from the state.

    Required scope

    Room.Marker must be inside <Room.Timeline>.

    Props

    2 documented props · Native <li> attributes supported

    Room.Marker props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: ChatMarker) => 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/marker.tsx

    Room.Runcontent · <li>

    One person's turn: their consecutive messages within five minutes, under one name. The first message is in scope for the whole turn, so `Room.MessageAuthor`, `Room.MessageAvatar` and `Room.MessageTime` placed on the turn say who and when once. `data-state` is `mine` or `theirs`, which is what a skin sides a turn by.

    Required scope

    Room.Run must be inside <Room.Timeline>.

    Props

    2 documented props · Native <li> attributes supported

    Room.Run props
    PropTypeDefault
    children

    Content rendered inside this part.

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

    Room.RunMessagesscope

    The messages of one turn, each in its own scope with where it is: sending, sent or failed.

    Required scope

    Room.RunMessages must be inside <Room.Run>.

    Props

    2 documented props

    Room.RunMessages props
    PropTypeDefault
    group

    What the messages are grouped in: a Motion presence group lets each one arrive.

    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/run-messages.tsx

    Room.Messagecontent · <li>

    One message. `data-state` is `mine` or `theirs`, which is what a skin sides by; a message of yours still on its way is `data-pending`, and one that never went is `data-invalid`. `data-flash` is set for a moment when a quote of it is followed here.

    Required scope

    Room.Message must be inside <Room.Messages>.

    Props

    2 documented props · Native <li> attributes supported

    Room.Message props
    PropTypeDefault
    children

    Content rendered inside this part.

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

    Room.MessageAuthorcontent · <span>

    Who said it. Looked up in the roster rather than carried on the message, because a name the sender put in the message would be the name they had when they sent it — and a rename mid-conversation would leave half the transcript addressed to somebody who no longer exists.

    Required scope

    Room.MessageAuthor must be inside <Room.Messages>.

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

    Props

    2 documented props · Native <span> attributes supported

    Room.MessageAuthor 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/message-author.tsx

    Room.MessageAvatarcontent · <span>

    The face of whoever said it, their picture or else their initials, with their colour as `--peer` and its ink as `--peer-ink`. Its own part rather than `Room.PeerAvatar` inside a message, because a message outlives the person: somebody who left is still in the transcript and is in nobody's roster, and a part that needed a peer scope would have to throw for them. Here they keep a colour derived from the id the message carries, so the conversation does not change shape when they close the tab. → ./peer-look.ts

    Required scope

    Room.MessageAvatar must be inside <Room.Messages>.

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

    Props

    2 documented props · Native <span> attributes supported

    Room.MessageAvatar 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/message-avatar.tsx

    Room.MessageBodycontent · <span>

    Transcript is the chat's scroller, role="log". Timeline lists turns and markers in order; Run is one person's consecutive messages under one name and RunMessages its messages; Marker is an arrival, a departure or a presentation. Messages and Message are the flat alternative. MessageAuthor, MessageAvatar, MessageBody and MessageTime describe the message in scope, and MessageSegments splits its text into SegmentText and SegmentLink. NoMessages, NewMessages and ScrollToLatest handle the empty list and the reader who scrolled up. Nothing is kept: the chat lives as long as the room.

    Required scope

    Room.MessageBody must be inside <Room.Messages>.

    Props

    2 documented props · Native <span> attributes supported

    Room.MessageBody 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/message-body.tsx

    Room.MessageTimecontent · <time>

    When it was said. `RoomMessage.at` has been on the wire since the first version of this service and no screen ever showed it. A `<time>` with the full moment in `dateTime` and the hour and minute as text: the long form is what assistive technology and a copy-paste get, and the short form is what a bubble has room for.

    Required scope

    Room.MessageTime must be inside <Room.Messages>.

    Props

    2 documented props · Native <time> attributes supported

    Room.MessageTime 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/message-time.tsx

    Room.MessageSegmentsscope

    A message's text as words and links, each in its own scope.

    Required scope

    Room.MessageSegments must be inside <Room.Messages>.

    Props

    1 documented props

    Room.MessageSegments 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/message-segments.tsx

    Room.SegmentTextcontent

    Words, as they were typed. Nothing for a link, which `Room.SegmentLink` draws.

    Required scope

    Room.SegmentText must be inside <Room.MessageSegments>.

    Props

    2 documented props

    Room.SegmentText 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/segment-text.tsx

    Room.Quoteaction · <button>

    What this message answers, as it was when it was answered: a button that scrolls to the original while the list still holds it. Nothing on a message that answers nobody. The name is looked up in the roster, like an author's, and the one the quote carried is for somebody who has since left. → ./message-author.tsx

    Required scope

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

    Props

    2 documented props · Native <button> attributes supported

    Room.Quote props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: QuoteState) => 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/quote.tsx

    Room.Replyaction · <button>

    Answer this message: the composer quotes it, and the field takes the cursor.

    Required scope

    Room.Reply must be inside <Room.RunMessages>.

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

    Props

    2 documented props · Native <button> attributes supported

    Room.Reply 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/reply.tsx

    Room.Sendinggate

    This message of yours is waiting for the room: reconnecting, or not joined yet.

    Props

    1 documented props

    Room.Sending 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/sending.tsx

    Room.NotSentgate

    This message of yours never found its way to the room, and is still here to try again.

    Props

    1 documented props

    Room.NotSent 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/not-sent.tsx

    Room.Retryaction · <button>

    Send a failed message again, under the id it already has, so it cannot arrive twice.

    Required scope

    Room.Retry must be inside <Room.RunMessages>.

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

    Props

    2 documented props · Native <button> attributes supported

    Room.Retry 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/retry.tsx

    Room.MessageReactionsscope

    The emoji left on this message, each once with everybody who left it.

    Required scope

    Room.MessageReactions must be inside <Room.Messages>.

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

    Props

    1 documented props

    Room.MessageReactions 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/message-reactions.tsx

    Room.ReactionToggleaction · <button>

    One emoji on a message and how many left it: pressing it adds yours or takes it back.

    Required scope

    Room.ReactionToggle must be inside <Room.MessageReactions>.

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

    Props

    2 documented props · Native <button> attributes supported

    Room.ReactionToggle props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: ReactionState) => 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/reaction-toggle.tsx

    Room.ReactToaction · <button>

    Leave this emoji on the message in scope, or take it back.

    Required scope

    Room.ReactTo must be inside <Room.Messages>.

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

    Props

    3 documented props · Native <button> attributes supported

    Room.ReactTo 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-to.tsx

    Room.Messagesscope

    What has arrived while this browser has been in the room, and nothing else. A late joiner sees an empty list, which is what the service promises rather than a bug. → services/realtime/ports.ts

    Required scope

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

    Props

    1 documented props

    Room.Messages 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/messages.tsx

    Room.NewMessagesgate

    Something arrived while somebody was reading further up. Only then: a button offering to jump to the bottom of a list that is already at the bottom is a button that does nothing, and one offering "new messages" when none have arrived is a lie. → docs/rules/capability-honesty.md

    Required scope

    Room.NewMessages must be inside <Room.Transcript>.

    Props

    1 documented props

    Room.NewMessages props
    PropTypeDefault
    children

    Content rendered inside this part.

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

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

    Source: @slices.sh/realtime/src/headless/new-messages.tsx

    Room.ScrollToLatestaction · <button>

    Back to the newest message, and following again from there.

    Required scope

    Room.ScrollToLatest must be inside <Room.Transcript>.

    Props

    2 documented props · Native <button> attributes supported

    Room.ScrollToLatest props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { unseen: 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/scroll-to-latest.tsx

    Room.Composeraction · <form>

    The one form a message is sent from. It reads its input by name on submit rather than tracking every keystroke, so the input stays uncontrolled and a skin may put anything around it.

    Required scope

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

    Props

    2 documented props · Native <form> attributes supported

    Room.Composer 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/composer.tsx

    Room.ReplyDraftcontent · <div>

    Where a composer says which message it is answering, for as long as it is: nothing while it writes to everybody. `Room.ReplyingTo` names the message and `Room.CancelReply` lets it go. Answering another message is a new draft, so it arrives as one.

    Required scope

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

    Props

    3 documented props · Native <div> attributes supported

    Room.ReplyDraft props
    PropTypeDefault
    group
    GateGroupComponent—
    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/reply-draft.tsx

    Room.ReplyingTocontent · <span>

    The message being answered: who said it and a line of what they said, for its children.

    Required scope

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

    Props

    2 documented props · Native <span> attributes supported

    Room.ReplyingTo props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: ReplyingState) => 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/replying-to.tsx

    Room.CancelReplyaction · <button>

    Write to everybody again rather than answer one message. Escape in the field does the same.

    Required scope

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

    Props

    2 documented props · Native <button> attributes supported

    Room.CancelReply 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/cancel-reply.tsx

    Room.MessageInputinput · <input>

    What is being typed. `name="message"` is what the composer reads.

    Props

    2 documented props · Native <input> attributes supported

    Room.MessageInput 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/message-input.tsx

    Room.MessageFieldinput · <textarea>

    What is being typed, over as many lines as it takes: Enter sends, Shift+Enter breaks the line, and nothing is sent in the middle of composing a character with an input method. `name="message"` is what the composer reads. Typing here tells the room you are. Choosing a message to answer brings the cursor here, and Escape stops answering it.

    Props

    2 documented props · Native <textarea> attributes supported

    Room.MessageField 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/message-field.tsx

    Room.Sendaction · <button>

    The one submit in the composer: everything else beside it is `type="button"`. → docs/rules/one-submit-per-form.md Enabled while the room is joining or reconnecting as well as connected, because a message typed then is kept and sent the moment the room is back, drawn as sending until it goes. Disabled only where it could never go: a room that refused, failed or was left. → docs/rules/capability-honesty.md

    Required scope

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

    Props

    2 documented props · Native <button> attributes supported

    Room.Send props
    PropTypeDefault
    children

    Content rendered inside this part.

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

    Room.Typinggate

    Somebody else is typing, and who. Not announced: a live region saying it per keystroke is noise, and the message itself is what gets said when it lands. Your other tab is you, whether or not the screen counts people: a line saying you are typing tells you nothing, and under your own name it reads "You is typing".

    Required scope

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

    Props

    1 documented props

    Room.Typing props
    PropTypeDefault
    children

    Content rendered inside this part.

    ReactNode | ((state: { names: string[] }) => ReactNode)—

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

    Source: @slices.sh/realtime/src/headless/typing.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 →