can-mirror playground
can-mirror is the “everything syncs” capability. Put it on any element and playhtml observes the DOM — input values, checked state, pseudo-class data attributes, attribute changes, child adds/removes — then replays those mutations on every other reader.
This page hosts every edge case we’ve found useful, one section each. Open it in two browser windows side by side and poke at each demo — everything should mirror.
Nothing on this page requires custom JavaScript. It’s all plain HTML with can-mirror on a root element. The docs site’s shared playhtml.init() picks each one up automatically.
:hover
Section titled “:hover”The synced data-playhtml-hover attribute lets CSS target the hover state from across the network. Hover over the box in one tab — it turns green in every other tab.
Hover me
<style>
#pg-mirror-hover[data-playhtml-hover] {
background: #4caf50;
color: white;
}
</style>
<div can-mirror id="pg-mirror-hover">Hover me</div>
:focus
Section titled “:focus”Click the input — the blue outline shows for every client until you blur.
Text input
Section titled “Text input”Type — the value replicates.
Textarea
Section titled “Textarea”Checkbox
Section titled “Checkbox”Radio buttons
Section titled “Radio buttons”Select (dropdown)
Section titled “Select (dropdown)”Range slider
Section titled “Range slider”Color picker
Section titled “Color picker”Details / summary
Section titled “Details / summary”Expand and collapse — the native open attribute mirrors automatically.
Click to expand
Hidden until the details element is opened. The open
attribute is a regular DOM attribute, so the MutationObserver catches it
automatically.
contenteditable
Section titled “contenteditable”Type, paste, delete — every mutation lands on every client. Try pressing Enter inside the list to add items; delete them by selecting and pressing Backspace.
Edit this text collaboratively!
- First item — type to edit, Enter for new line
- Second item
Mixed form inputs
Section titled “Mixed form inputs”A single can-mirror wrapping a form with heterogeneous inputs — all of them sync together.
Play from devtools
Section titled “Play from devtools”can-mirror listens to a MutationObserver on the element. It doesn’t care whether a mutation comes from a click, a React re-render, or your browser devtools. You can verify that last one right now.
Open your browser devtools (F12) and paste this into the console:
const el = document.querySelector("#pg-mirror-hover");
el.classList.add("is-flag");
el.setAttribute("data-you-changed-this", "yes");
Then open this page in another tab and inspect the element — the class and the custom attribute will be there too.
Same trick for programmatic child changes:
const el = document.querySelector("#pg-mirror-editable-list");
const li = document.createElement("li");
li.textContent = "added via console";
el.appendChild(li);
Anything a MutationObserver would fire for — attribute additions and removals, childList inserts, characterData changes — can-mirror sends across. That’s the whole capability.
What doesn’t mirror (yet)
Section titled “What doesn’t mirror (yet)”- Scroll position on nested containers —
scrollTop/scrollLeftaren’t DOM mutations, so the observer doesn’t catch them. - Media playback —
<video>/<audio>currentTimeisn’t attribute-driven. - Canvas / WebGL — pixel changes don’t emit mutations.
- File inputs —
<input type="file">’s selected file isn’t serializable across the wire.
For these, use element data with can-play and explicitly sync the bit you care about.
When to reach for can-mirror vs can-play
Section titled “When to reach for can-mirror vs can-play”- can-mirror: the DOM shape is the source of truth. Forms, collaborative scratchpads, shared to-do checkboxes, native UI elements.
- can-play: you want a custom data shape with your own render function. Chat messages, multi-dimensional state, counts, lists where the visual is computed.
Rule of thumb: if your element would work correctly with just contenteditable and form inputs, can-mirror is the least-code path. The moment you reach for JSON.stringify, switch to can-play.