Skip to content

Page status board

Each reader picks a status, and everyone sees each person’s status next to their name. A status is page presence: everyone can see it, only you can change yours, and it’s gone when you leave.

Live exampleRoom connecting…
Remix
Test it with another person
  1. Keep this page open in your normal window.
  2. Copy the private-window link, then paste it into a private or incognito window.
  3. Interact in either window and watch the other one update.

Copy the code

Both versions use the same shared data and behavior. The live playground runs the Vanilla HTML version.

Save this as index.html, or open it in the playground to test and change it.

Remix

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Page status board</title>
  <style>
    :root {
      color: #1c1c1c;
      background: #f4efe5;
      font-family: ui-sans-serif, system-ui, sans-serif;
    }
    * { box-sizing: border-box; }
    body { display: grid; min-height: 100vh; margin: 0; padding: 1.5rem; place-items: center; }
    .picker {
      display: flex;
      flex-wrap: wrap;
      gap: 0.4rem;
    }
    .picker button {
      padding: 0.35rem 0.75rem;
      border: 1.5px solid #1c1c1c;
      border-radius: 999px;
      background: white;
      font: inherit;
      cursor: pointer;
    }
    /* Your current status is highlighted. */
    .picker button[aria-pressed="true"] {
      background: #e8a63a;
    }
    .people {
      display: grid;
      gap: 0.35rem;
      margin: 1rem 0 0;
      padding: 0;
      list-style: none;
    }
    .people li {
      display: flex;
      align-items: center;
      gap: 0.55rem;
      padding: 0.4rem 0.6rem;
      border: 1.5px solid #1c1c1c;
      border-radius: 6px;
      background: white;
    }
    .people .dot {
      width: 14px;
      height: 14px;
      border: 1.5px solid #1c1c1c;
      border-radius: 50%;
    }
    .people .status {
      margin-left: auto;
      color: #6a6a66;
    }
  </style>
</head>
<body>
  <main>
    <div id="picker" class="picker"></div>
    <ul id="people" class="people"></ul>
  </main>

  <script type="module">
    import { playhtml } from "https://unpkg.com/playhtml@latest";

    const STATUSES = ["reading", "coding", "taking a break"];
    const picker = document.getElementById("picker");
    const list = document.getElementById("people");

    // playhtml.people is available once playhtml has connected.
    playhtml.ready.then(() => {
      const { people } = playhtml;

      for (const status of STATUSES) {
        const button = document.createElement("button");
        button.textContent = status;
        button.addEventListener("click", () => {
          // Picking your current status again clears it (null removes the key).
          const mine = people.me.presence.status;
          people.setMyPresence("status", mine === status ? null : status);
        });
        picker.append(button);
      }

      // Calls back now, then when someone joins, leaves, renames, or changes
      // their status.
      people.onChange("status", (everyone) => {
        const mine = people.me.presence.status;
        for (const button of picker.children) {
          button.setAttribute("aria-pressed", String(button.textContent === mine));
        }
        list.replaceChildren(
          ...everyone.map((person) => {
            const row = document.createElement("li");
            const dot = document.createElement("span");
            dot.className = "dot";
            dot.style.background = person.color;
            const name = document.createElement("span");
            name.textContent = (person.name ?? "anonymous") + (person.isMe ? " (you)" : "");
            const status = document.createElement("span");
            status.className = "status";
            status.textContent = person.presence.status ?? "…";
            row.append(dot, name, status);
            return row;
          }),
        );
      });
    });

    await playhtml.init({ developmentMode: true });
  </script>
</body>
</html>

setMyPresence(key, value) sets one key of your page presence. Pass null to clear it:

playhtml.people.setMyPresence("status", "reading");
playhtml.people.setMyPresence("status", null);

onChange("status", callback) calls back now, then when someone joins, leaves, renames, or changes their status. Each person’s status is at person.presence.status:

playhtml.people.onChange("status", (everyone) => {
  for (const person of everyone) {
    console.log(person.name, person.presence.status);
  }
});

In React, usePeople("status") re-renders on the same changes.

See page presence for the full API.