# playhtml > playhtml makes HTML elements collaborative and real-time. Add an attribute like `can-move` or `can-toggle` to an element with an `id`, or register a custom `can-play` element, and its state syncs live between everyone on the page. Works with vanilla HTML (`playhtml` on npm or unpkg) and React (`@playhtml/react`). Read this whole file before writing playhtml code. The guide below covers the APIs, which data type to use, and the mistakes that silently break sync. ## Docs - [Getting started](https://playhtml.fun/docs/getting-started/): install and first element, vanilla HTML and React - [Built-in elements](https://playhtml.fun/docs/capabilities/): can-move, can-toggle, can-spin, can-grow, can-duplicate, can-hover, can-mirror - [Custom elements](https://playhtml.fun/docs/custom-elements/): can-play with playhtml.register - [Core concepts](https://playhtml.fun/docs/concepts/): what playhtml shares and which kind of state to use - [Data best practices](https://playhtml.fun/docs/advanced/data-best-practices/): shaping and writing shared data safely - [People and presence](https://playhtml.fun/docs/data/presence/): who's here (`playhtml.people`), what each person is doing right now, and cursors - [Events](https://playhtml.fun/docs/data/events/): one-shot broadcasts like confetti - [React API](https://playhtml.fun/docs/reference/react-api/) - [Examples](https://playhtml.fun/docs/examples/) playhtml makes HTML elements collaborative and real-time via Yjs CRDTs. ## The Person's Idea Drives the Build playhtml is for people making their own internet playgrounds. The experience is theirs to design; your job is to make their idea work, not to fill in the idea for them. - **Build only what was asked.** Do not add mechanics, rules, sounds, music, animations, motion, scoring, timers, themes, or extra features the person did not describe. A "musical chairs with cursors" request does not authorize you to pick the music, make cursors circle, or invent the elimination rules. - **Ask about experience decisions before building.** If the request leaves open how the interaction works (what people do, what happens in response, how it starts and ends, what everyone else sees), list those open questions briefly and wait for answers. Do not pick for them. - **Confirm the plan first for anything bigger than a single element.** Restate the interaction in a few plain sentences and get a yes before writing code. - **Fit the page, with a lighter touch than usual.** On a page with its own style, match its character: reuse its colors, fonts, borders, illustrations, and level of playfulness, so the new element looks like it was always there. A colorful, hand-made page should get colorful, hand-made-feeling pieces, not a single-color placeholder. On a blank page, keep the styling modest and let the interaction be the interesting part. In both cases, lean a bit more restrained than you normally would: the look is the person's call, so leave room for them to make it theirs. If the look clearly matters to them, ask what they want. - **Offer ideas, don't ship them.** If you see a good addition, mention it in one line after the working version. The person decides. If the person says to surprise them or to use your judgment, you may make those choices, and say which ones you made. ## Before Implementing — Ask These Questions Along with any open experience decisions above, if the request is ambiguous on ANY of these, **stop and ask**: 1. **Persistence**: Should data survive page refresh? (`defaultData`=yes, presence=no) 2. **Shared vs per-user**: Should all users see the same state, or does each user have their own? 3. **Vanilla HTML or React?** These determine which API and data type to use. Getting them wrong means a rewrite. ## Data Types | Type | Persists? | Syncs? | Use for | |------|-----------|--------|---------| | `defaultData` | Yes | Yes | Positions, counts, messages, toggles | | presence: `defaultPresence` (element) / `playhtml.people.setMyPresence` (page) | No | Yes | Per-person typing, ready, hover, or selection state | | `dispatchPlayEvent` | No | One-shot | Confetti, notifications | | `localStorage` | Yes | No | Per-user flags ("has reacted") | People: every person is a `Person` (`{ pid, name, color, isMe, presence }`). `playhtml.people.everyone` is everyone on the page including you (filter `!person.isMe` for others); `playhtml.people.me` is you (assign `me.name` / `me.color` to save and show them). Key per-person data by `pid`. Page presence: `playhtml.people.setMyPresence(key, value)` sets one key (`null` clears it), `playhtml.people.onChange(keys?, cb)` fires now and on changes (skips cursor moves unless `"cursor"` is in keys). Cursors are at `person.presence.cursor`. `playhtml.people` is available after `playhtml.ready`. Rooms: `playhtml.createPresenceRoom(name).people`, same API. React: `usePeople(keys?)` → `{ everyone, me, setMyPresence }`, also on `usePlayContext().people`. Elements use the same names: `defaultPresence`, and `everyone` / `me` / `setMyPresence(value | mutator)` in callbacks and on handles (`handle.onEveryoneChange(keys?, cb)`). Deprecated but working: `playhtml.users`, `User`, `playhtml.presence`, `useUsers`, `usePresence`, `useCursorPresences`, `usePlayerIdentity`, element `live`/`users`/`setLive`. ## Critical Rules - Every element MUST have a unique `id` attribute — without it, sync silently fails - Vanilla HTML: `import { playhtml } from "https://unpkg.com/playhtml@latest"` only works inside ` ``` Every other vanilla snippet in this guide that uses `playhtml` goes inside the same `