Presence
Show who else is in the document right now — one hook, no signalling server, no state of your own.
Two people open the same creature entry. One of them starts rewriting the lore. The other one, seeing nothing, starts rewriting it too.
The data will merge — that's the codec's job. But the awkwardness is a presence problem, and it's solved by showing each of them that the other one is there.
You will learn
- How to list everyone currently in a document
- What each peer gives you, and what it deliberately doesn't
- Where presence belongs, and where it doesn't
One hook
import { useDocument, useDocumentPresence } from "@vvd/sdk"
const { data, actions, handle } = useDocument(coords, codec)
const peers = useDocumentPresence(handle)handle is the opaque document handle useDocument already gave you. peers is everyone
currently in this document, deduped per person, updating live as they arrive and leave.
Each peer is four fields:
userId | stable per person — use it as your React key |
name | their display name |
color | their presence colour, assigned by the platform and consistent everywhere |
isSelf | true for the local user, so you can label or skip yourself |
There's also an optional avatarMediaId when they have a picture.
That's the whole API. There is no presence channel to open, no heartbeat to send, no "who's online" endpoint, and no state of your own to keep in sync.
Two people, live
Both panes below are real: two identities, two hosts, two awareness channels, relayed to each other exactly the way two browsers would be. Move your pointer inside one pane and watch the other one draw it.
The coloured chips are drawn by the tool, from useDocumentPresence. The cursors are
drawn by the surface around it, which is the right division of labour: pointer capture and
cursor rendering belong to whatever owns the viewport, not to every tool that would like to
show who's here.
Where presence belongs
A facepile is the minimum, and it costs you four lines. Put it somewhere permanent — a header, a corner — so it's visible before anyone starts typing, not after.
Beyond that, presence is most useful where two people can collide:
- On a canvas, per-user cursors (the
canvastemplate ships them). - In prose, live carets —
<CollaborativeText>renders them for you, with no presence code at all. - On a row or a field, a small marker showing who's editing it right now.
Presence is ephemeral by design: it lives only while people are connected and is never written to the document. Don't try to persist it, and don't derive anything durable from it.
Broadcasting something of your own
Reading peers is half of awareness; the other half is broadcasting a small ephemeral payload
of your own — a pointer position, a selection — that peers should see live but that must
never land in the document.
useCollabPresence(handle) is the typed way in
(setLocal publishes your state, peers[].state reads everyone else's);
useDocumentAwareness(handle) hands you the
document's raw Awareness object for when you need the protocol itself — the
Blocks starter kit broadcasts each peer's hand position
over it with setLocalStateField, which is why a drag there reads as a person rather than a
jump. Both ride the same channel as the facepile: lost on disconnect by design, and never
anything to persist.
Two things that will bite you
Presence is per document, not per world. useDocumentPresence(handle) answers "who is
in this document". Somebody in a different document of the same world is not in your list,
and that's correct.
handle is null until the document opens. The hook returns an empty array in that
window rather than throwing, so give your facepile a minHeight — otherwise your layout
jumps the moment the first peer appears. The example above does exactly that.
When it doesn't work
You only ever see yourself.
Presence rides the document's connection. If the document opened as a local-only or
read-only snapshot, there is no shared channel to announce yourself on. Check status is
ready, and check you're looking at the same document in both places.
The chips flicker as people move around.
You're keying on something unstable. Key on peer.userId.
Someone's name is missing or their colour changed. Names and colours come from the platform's identity, not from your tool. If a user changes their profile, it re-stamps everywhere live — including in documents already open.
Recap
useDocumentPresencelists who is in the document right now, with names and colours.- Presence is ephemeral: it lives in the awareness channel, never in your document.
- The platform draws carets in collaborative text for you; your job is the peripheral signals.
- Under a host with no presence, the hook returns an empty list and your tool still renders.