> 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 `