Skip to content
Get started— browse docs
On this page

Step 7: See each other

useDocumentPresence lists who's in the document right now — no channel to open, no state of your own.

The platform already drew a cursor for you in the last step — that came free, from the surface around the tool. Now make your own UI say who's here, with one hook.

Why does presence come off handle, the thing useDocument returned? Because presence is a per-document question — "who's in this document", not "who's online" — so it rides the same live connection the document already opened. That's the whole design: no channel of your own to open or tear down, and nothing written into the document itself. Presence is ephemeral on purpose — when someone closes the tab, they're simply gone, with no stale row to clean up.

Add this component to src/tool.tsx, above Board. DocumentHandle is the SDK's name for the thing useDocument returns as handle — the prop is typed because the scaffold's tsconfig is strict, and an untyped { handle } destructuring is an implicit-any error:

src/tool.tsx
function PresenceStrip({ handle }: { handle: DocumentHandle | null }) {
  const others = useDocumentPresence(handle).filter((p) => !p.isSelf)
  if (others.length === 0) return null
  return (
    <span style={{ display: "flex", alignItems: "center" }}>
      {others.map((p) => (
        <span
          key={p.userId}
          style={{
            width: 22,
            height: 22,
            borderRadius: "50%",
            marginLeft: -6,
            display: "inline-flex",
            alignItems: "center",
            justifyContent: "center",
            background: p.color,
            color: "#fff",
            fontSize: 11,
            fontWeight: 700,
          }}
        >
          {p.name ? p.name.slice(0, 1).toUpperCase() : ""}
        </span>
      ))}
    </span>
  )
}

Then wire it up — three small edits, all in src/tool.tsx (the codec half at the top of the pane is untouched this step): add handle to what BestiaryView pulls out of useDocument, thread it into Board as a prop, and render <PresenceStrip handle={handle} /> next to the Add button. Don't forget useDocumentPresence — and type DocumentHandle — in the @vvd/sdk import. (Splitting rule for that merged import block, as ever: defineStateCodec and field belong to src/codec.ts; everything else — PresenceStrip and newId included — is src/tool.tsx.)

Add presence
src/tool.tsx Lines 86 and 89 highlighted.

Edit and the example re-runs. Tab indents; press Escape to leave the editor.

Running · your edits, live
Starting the example…

You should see: nothing changes yet — there's only one of you in this pane. The next demo proves it works, with a second identity for real.

Two people, one document
Move your pointer inside a pane
Starting the example…

Move your pointer inside a pane. The other one draws it. Add a note in one — the coloured chip appears in both headers, because both panes are running your PresenceStrip, reading the same useDocumentPresence(handle).

Recap

  • useDocumentPresence(handle) lists everyone in the document right now — userId, name, color, isSelf.
  • It's ephemeral: presence lives in the awareness channel, never in your document.
  • Cursors are drawn by the surface around your tool; the facepile is yours to build.

Next steps