JSFiddle - React, Tailwind, and code Playground
by nim_a101
HTML
<div class="toolbar">
<button id="refresh">Refresh</button>
<span id="version"></span>
</div>
<div id="grid" class="grid"></div>
CSS
:root {
--cell: 34px; /* cell size */
--grid-border: #c9ced6; /* outer border */
--cell-border: #e5e9ef; /* inner lines */
--on: #2b7fff1a; /* checked fill */
--on-border: #2b7fff66; /* checked outline */
}
.grid {
display: grid;
grid-template-columns: repeat(10, var(--cell));
grid-auto-rows: var(--cell);
border: 1px solid var(--grid-border);
width: max-content;
background: white;
}
.cell {
border-right: 1px solid var(--cell-border);
border-bottom: 1px solid var(--cell-border);
}
/* remove right/bottom border on edges */
.cell:nth-child(10n) { border-right: 0; }
.cell:nth-last-child(-n + 10) { border-bottom: 0; }
/* make the checkbox fill the entire cell and remove native look */
.cell > input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
width: 100%;
height: 100%;
margin: 0;
outline: none;
cursor: pointer;
background: transparent;
transition: background 80ms ease, box-shadow 80ms ease;
}
/* checked state = filled cell with subtle outline */
.cell > input[type="checkbox"]:checked {
background: var(--on);
box-shadow: inset 0 0 0 2px var(--on-border);
}
/* keyboard focus ring across full cell */
.cell > input[type="checkbox"]:focus-visible {
box-shadow: inset 0 0 0 2px #00000066;
}
/* optional hover affordance */
.cell > input[type="checkbox"]:hover {
background: #00000007;
}
JavaScript
const WORKER_BASE = 'https://checkbox-relay.nima-aghdaii.workers.dev';
const gridEl = document.getElementById('grid');
const versionEl = document.getElementById('version');
document.getElementById('refresh').addEventListener('click', loadState);
// build 10×10 once
(function build() {
for (let r = 0; r < 10; r++) {
for (let c = 0; c < 10; c++) {
const cell = document.createElement('div');
cell.className = 'cell';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.dataset.r = r;
cb.dataset.c = c;
cb.addEventListener('change', onToggle);
cell.appendChild(cb);
gridEl.appendChild(cell);
}
}
loadState();
})();
async function loadState() {
try {
const rsp = await fetch(`${WORKER_BASE}/state`, { headers: { Accept: 'application/json' } });
const data = await rsp.json();
versionEl.textContent = `v${data.version}`;
const boxes = gridEl.querySelectorAll('input[type=checkbox]');
for (const box of boxes) {
const r = +box.dataset.r, c = +box.dataset.c;
box.checked = !!(data.checked?.[r]?.[c]);
}
} catch (e) {
versionEl.textContent = 'load failed';
console.error(e);
}
}
async function onToggle(e) {
const box = e.currentTarget;
const row = +box.dataset.r, col = +box.dataset.c, checked = box.checked;
try {
await fetch(`${WORKER_BASE}/toggle`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ row, col, checked })
});
} catch (err) {
// revert if server failed
box.checked = !checked;
console.error(err);
}
}