Skip to content

Shared click counter

This counter stores { count: 0 } as shared data. Each click mutates the current count so concurrent clicks merge without replacing newer values.

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>Shared click counter</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; }
    main { width: min(40rem, 100%); }
    h1 { margin: 0 0 0.35rem; font-size: clamp(2rem, 8vw, 3.5rem); line-height: 1; }
    .intro { margin: 0 0 1.25rem; line-height: 1.5; }
    main { text-align: center; }
    .counter {
      display: inline-flex;
      align-items: center;
      gap: 0.5rem;
      padding: 0.5rem 1rem;
      border: 1.5px solid #65795d;
      border-radius: 6px;
      background: #f4efe5;
      color: #1c1c1c;
      cursor: pointer;
      font: 1.1rem/1 ui-monospace, monospace;
      transition: transform 80ms, box-shadow 120ms;
    }
    .counter:hover { box-shadow: 2px 2px 0 #1c1c1c; }
    .counter:active { transform: translateY(1px); }
    .count { color: #65795d; font-weight: 600; }
  </style>
</head>
<body>
  <main>
    <button
      id="ph-docs-counter"
      class="counter"
      type="button"
      can-play
      aria-label="Increment the shared counter"
    >
      <span aria-hidden="true">❤️</span>
      <span class="count" data-count>0</span>
    </button>
  </main>

  <script type="module">
    import { playhtml } from "playhtml";

    const counter = document.getElementById("ph-docs-counter");

    counter.defaultData = { count: 0 };
    counter.onClick = (_event, { setData }) => {
      setData((draft) => {
        draft.count += 1;
      });
    };
    counter.updateElement = ({ element, data }) => {
      element.querySelector("[data-count]").textContent = String(data.count);
    };

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

Write from the user event:

counter.onClick = (_event, { setData }) => {
  setData((draft) => {
    draft.count += 1;
  });
};

Don’t rebuild the value from rendered text. The rendered value may be behind a remote update.

updateElement reads shared data without writing it:

counter.updateElement = ({ element, data }) => {
  element.querySelector("[data-count]").textContent = String(data.count);
};

See Data essentials for mutator and replacement writes.