Skip to content
playhtml
support

Core concepts

playhtml shares two things between everyone on a page: the things on the page (a lamp that’s on, a hat that’s been dragged, a vote count) and the people looking at it (who’s here, what color they are, what they’re doing right now). Everyone visiting the same url shares one room, so whatever you change shows up for everyone else too.

A page running playhtml with four callouts: element data bound to a specific element, page data read by name anywhere on the page, people with their cursors and what they're doing right now, and one-off events. Element and page data are saved; people's presence and events are live only. Faded pages behind show that everyone on the same url shares one room.

Shared data is saved. It survives reloads, and someone who opens the page tomorrow sees it.

  • Element data belongs to one element: a toggle’s on/off, a draggable’s position, a shared count. Built-in elements come with theirs; custom elements set it with defaultData.
  • Page data (playhtml.createPageData) has a name instead of an element: a page-wide counter, a shared prompt, an open vote. See page-level data.

Each element’s data is stored under its id, so the element with the same id on everyone’s page stays in sync:

Two browsers open to the same page. Each interactive element (a lamp with can-toggle, a hat with can-move, a wheel with can-spin, a balloon with can-grow, and a custom can-play guestbook holding a list of entries) has its own data object keyed by element id, saved by the playhtml server. A change on one page syncs through the server and the matching element updates on the other page. Above the elements, a "people here" panel shows each person's presence, which is gone when they leave: Ana is marked as you on her page, and Sam shows a typing indicator on both pages.

People: who’s here and what they’re doing

Section titled “People: who’s here and what they’re doing”

People is used to reference the actual users on a page and data about them. For example, playhtml.people.everyone is everyone on the page right now, including you. Each person looks like this:

type Person = {
  pid: string;
  name?: string;
  color: string;
  isMe: boolean;
  presence: object;
};
  • name and color are personal properties that save across visits.
  • presence contains any properties that are transient and only save while someone is on the page. For example, someone’s live status, if they’re typing, etc. Everyone can see it, each person can only set their own, and it disappears when they leave.

Presence lives on the page or on one element, and both use the same names:

  • On the page: playhtml.people.setMyPresence("status", "typing"), read from each person’s presence.status. Cursors are page presence that playhtml fills in for you, at person.presence.cursor.
  • On an element: inside an element, everyone, me, and setMyPresence describe the people using that element and their presence on it. “Who’s hovering this card” is element presence.

For presence shared across several pages, use a presence room. See people and presence and cursors.

Events (playhtml.dispatchPlayEvent) are one-off broadcasts that change nothing: confetti, a chime, a notification. Whoever is here when it fires sees it; nobody else ever does. See events.

An element can hold three kinds of state, and they differ in who sees them and how long they last:

Who sees itHow long it lastsWrite it withRead it as
dataEveryoneSaved, survives reloadssetDatadata
presenceEveryoneUntil you leavesetMyPresenceme.presence, and each person’s presence in everyone
localDataOnly you, in this tabUntil you reloadsetLocalDatalocalData

A lamp’s on/off is data. Who’s hovering it is presence. Whether your settings panel is open is local data.

Pick by lifetime first (“should this survive a reload?”), then by scope (“does this belong to one element, the whole page, or one person?”).

WantUseSurvives reloadScope
A toggle, position, count, or any state tied to one elementElement data (defaultData / can-play)YesOne element
A page-wide counter, prompt, or vote not tied to a DOM nodePage data (playhtml.createPageData)YesOne page
”Who is here right now?” / “How many readers?”People (playhtml.people.everyone)NoPer person
”Who’s typing?” / a live status for the whole pagePage presence (playhtml.people.setMyPresence)NoPer person
”Who’s hovering this card?” / state about one elementElement presence (defaultPresence, setMyPresence)NoPer person, one element
”Where is everyone’s cursor?”Cursors (person.presence.cursor)NoPer person
Confetti burst, chime, notificationEvents (dispatchPlayEvent)No (fires once)Broadcast
”How many people reacted to this post?”Element data (a count field)YesOne element

A few habits keep shared data small, fast, and free of conflicts. Each one links to the full explanation and examples.

  • Use the mutator form for counts and lists. setData((draft) => { draft.count += 1 }) merges with other people’s edits; passing a whole new value replaces theirs. See mutator vs replacement.
  • Keep shapes flat, and don’t store values you can compute. Store the raw facts and derive totals or labels when you render. See shaping your data.
  • Don’t write on every mouse move or scroll. Write when the action finishes, or use presence or cursors for live motion. See high-frequency events.
  • Cap lists that grow forever. Trim old guestbook entries or messages as you add new ones. See bound growing lists.
  • Never write shared data from code that re-runs when that data changes, such as a React effect that depends on data or inside update. Write from clicks and other user actions. See why.

When elements go away for good, you can delete their data; see cleaning up. For how numbers, lists, and keyed collections merge, see merging data changes.

Element data is stored under the element’s id, so the id must stay the same across reloads and browsers. That’s how two visitors agree on which element is which. A hand-written id="my-lamp" is ideal. If you leave it out, playhtml falls back to hashing the element’s HTML, which can differ across browsers and break sync.

For many elements that share a selector (a row of magnets, a list of reactions), give each the same markup and add a single selector-id attribute instead of inventing unique ids. playhtml indexes the matches by position, so the N-th .magnet always gets the N-th slot of state. See Dynamic elements.