Switch with can-toggle
can-toggle stores one shared on or off value. Clicking the element adds or
removes its toggled class in every connected browser.
- Keep this page open in your normal window.
- Copy the private-window link, then paste it into a private or incognito window.
- 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.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Switch with can-toggle</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: 1rem; place-items: center; }
main { width: min(40rem, 100%); }
main { text-align: center; }
.toggle {
display: inline-flex;
align-items: center;
gap: 0.55rem;
padding: 0.55rem 1rem;
border: 2px solid #1c1c1c;
border-radius: 6px;
background: #ebe4d5;
box-shadow: 2px 2px 0 #1c1c1c;
color: #1c1c1c;
cursor: pointer;
font: 700 0.8rem/1 ui-monospace, monospace;
letter-spacing: 0.16em;
text-transform: uppercase;
transition: transform 120ms, box-shadow 120ms, background 120ms, color 120ms;
}
.toggle:hover { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 #1c1c1c; }
.toggle:active { transform: translate(2px, 2px); box-shadow: none; }
.toggle.toggled { background: #274b9e; color: #f4efe5; }
.dot {
width: 0.625rem;
height: 0.625rem;
border: 1px solid rgba(0, 0, 0, 0.35);
border-radius: 999px;
background: #79766f;
}
.toggle.toggled .dot { background: #e8a63a; box-shadow: 0 0 0 3px rgba(232, 166, 58, 0.25); }
.on-label { display: none; }
.toggle.toggled .off-label { display: none; }
.toggle.toggled .on-label { display: inline; }
</style>
</head>
<body>
<main>
<button id="ph-docs-toggle-demo" class="toggle" type="button" can-toggle>
<span class="dot" aria-hidden="true"></span>
<span class="off-label">off</span>
<span class="on-label">on</span>
</button>
</main>
<script type="module">
import { playhtml } from "playhtml";
// can-toggle adds or removes the toggled class.
await playhtml.init({ developmentMode: true });
</script>
</body>
</html>
Start with a React + TypeScript project, install the packages below,
then replace src/App.tsx with the component.
npm install playhtml @playhtml/react // ABOUTME: Shares one persistent on or off button across browsers.
// ABOUTME: Renders the established docs toggle from CanToggleElement data.
import { PlayProvider, CanToggleElement } from "@playhtml/react";
export default function App() {
return (
<PlayProvider initOptions={{ developmentMode: true }}>
<main>
<CanToggleElement>
{({ data }) => (
<button
id="ph-docs-toggle-demo"
type="button"
className={data.on ? "toggle is-on" : "toggle"}
aria-pressed={data.on}
>
<span className="dot" aria-hidden="true" />
<span>{data.on ? "on" : "off"}</span>
</button>
)}
</CanToggleElement>
</main>
<style>{`
:root { color: #1c1c1c; background: #f4efe5; font-family: system-ui, sans-serif; }
* { box-sizing: border-box; }
body { margin: 0; }
#root { display: grid; min-height: 100vh; padding: 1rem; place-items: center; }
main { text-align: center; }
.toggle {
display: inline-flex;
align-items: center;
gap: 0.55rem;
padding: 0.55rem 1rem;
border: 2px solid #1c1c1c;
border-radius: 6px;
background: #ebe4d5;
box-shadow: 2px 2px 0 #1c1c1c;
color: #1c1c1c;
cursor: pointer;
font: 700 0.8rem/1 ui-monospace, monospace;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.toggle:hover { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 #1c1c1c; }
.toggle:active { transform: translate(2px, 2px); box-shadow: none; }
.toggle.is-on { background: #274b9e; color: #f4efe5; }
.dot { width: 0.625rem; height: 0.625rem; border: 1px solid rgba(0, 0, 0, 0.35); border-radius: 999px; background: #79766f; }
.toggle.is-on .dot { background: #e8a63a; box-shadow: 0 0 0 3px rgba(232, 166, 58, 0.25); }
`}</style>
</PlayProvider>
);
} Style the shared state
Section titled “Style the shared state”Target the toggled class in CSS:
#ph-docs-toggle-demo.toggled {
background: #b9dfad;
}
The class represents shared state, so avoid adding a second click handler that
changes the same state. In React, CanToggleElement already handles the click.
Test the switch
Section titled “Test the switch”Open the private-window link, then click the switch in either window. Both switches should show the same value. Reloading keeps the current value.
See can-toggle for reset behavior and the complete React form.