> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # Layouts & Participant Views For multi-party video rooms, the SignalWire platform composes every participant's camera into a single **mixed video stream** — [`call.remoteStream$`][`remoteStream$`] emits that stream. A **layout** is the rule the server uses to composite it: grid, presenter + thumbnails, picture-in-picture, etc. Each room has a list of layouts the server allows; clients pick one, optionally pin who goes in which slot, and read back where everyone ended up. The server composes the video. The client picks the composition and draws overlays (name tags, mute icons, click targets) on top using percentage-based layer coordinates. ## Pick a layout Wire a picker to [`layouts$`] (available options), [`layout$`] (current selection), and [`setLayout()`] (mutator): ```js call.layouts$.subscribe((names) => { layoutPicker.innerHTML = names .map((n) => ``) .join(""); }); call.layout$.subscribe((current) => { layoutPicker.value = current ?? ""; }); layoutPicker.onchange = () => call.setLayout(layoutPicker.value, {}); ``` The available layout names are server-defined — they depend on the room's configuration. `setLayout` rejects with `InvalidParams` if you pass a name that isn't in `layouts$`, so either bind from the picker options (as above) or validate up front. The empty `{}` second argument means "let the server place participants automatically." It should be passed explicitly. To pin specific members into specific slots: ```js await call.setLayout("presenter", { [presenterId]: "reserved-0", // big slot [guestId]: "reserved-1", // sidebar }); ``` Slot names (`reserved-0`, `reserved-1`, `auto`, `standard-0`, …) are defined per-layout — see [`VideoPosition`]. Members not in the map are auto-placed. The local user can request their own position too with [`call.self.setPosition()`][`Participant.setPosition()`], gated by `capabilities.self.position`. The layout and the positions are **not applied atomically**: the SDK sets the layout first, then each member position in turn, so members may briefly flash into their default slots before being moved. The targets are validated up front, though — if a member you name has not yet had its call context delivered, the whole call rejects with [`ParticipantNotReadyError`](/docs/browser-sdk/v4/reference/errors/participant-not-ready-error) before anything is sent, and the layout is left unchanged. A member ID that isn't in the call at all is skipped with a warning. ## Render the layout Attach the mixed stream to a single `