Playground
A grid of chunky pixels to paint on, with a 16-colour palette. It's built for server-driven multiplayer: the board state is one attribute, cells (one hex digit per cell, row by row), which the server renders. Painting doesn't change cells. It emits sb-paint, your backend applies it, and the next server frame brings the new cells to everyone. While a pixel is in flight, it's drawn slightly faded.
Examples
Local mode
local paints the board's own buffer, so no server is needed. Drag to draw, pick a colour below, or focus the board and use the arrow keys plus Space.
<sb-pixel-board local grid size="24" style="--sb-pixel-board-size: 20rem"></sb-pixel-board>
Server-driven (the Showcase board)
This is the wiring the multiplayer board on the Showcase page uses. The server renders cells into every frame of the page's render stream, and painting is a command:
<sb-pixel-board id="board" size="48" cells="…rendered by the server…"
data-on:sb-paint="@post('/cmd/paint', {payload: {tabid: $tabid, color: evt.detail.color, cells: evt.detail.cells}})">
</sb-pixel-board>
No data-preserve-attr here: cells belongs to the server, and every morph brings the latest board.
Watch only
<sb-pixel-board readonly size="16" cells="0000000000000000000000055000000000000055550000000000005775000000000005579550000000000559955000000000055775500000000005555550000000000566665000000000f555555f0000000ff555555ff000000f05333350f0000000003333000000000000eddd0000000000000ee00000000000000000000000" style="--sb-pixel-board-size: 12rem"></sb-pixel-board>
Event
sb-paint bubbles out of the shadow root with detail: { color, cells }, where cells are the indices (y * size + x) touched since the last event. Events are batched every ~80 ms during a stroke, and fast drags are interpolated so strokes have no gaps.
Accessibility
The canvas is focusable: the arrow keys move a highlighted cursor, and Space or Enter paints. Its label always states the cursor position, that cell's colour and the selected colour. The palette is a radio group of labelled buttons.
Installation
Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.
<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://rocket.libretto.ch/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>
<sb-pixel-board local readonly size="16" cells="0000000000000000000000055000000000000055550000000000005775000000000005579550000000000559955000000000055775500000000005555550000000000566665000000000f555555f0000000ff555555ff000000f05333350f0000000003333000000000000eddd0000000000000ee00000000000000000000000" style="--sb-pixel-board-size: 7.5rem"></sb-pixel-board>
<!-- Or skip the autoloader and load just this component: -->
<!-- <script type="module" src="https://rocket.libretto.ch/c/pixel-board/pixel-board.js"></script> -->API reference
Props
| Attribute | Type | Default | Description |
|---|---|---|---|
size | number | 48 | Cells per side. |
cells | string | "" | Board state: one hex digit (palette index) per cell, row by row. Usually server-owned. |
palette | array | [
"#080D1D",
"#1B1F3B",
"#283552",
"#7785A8",
"#AEBBDD",
"#F3F4FA",
"#8C6BFF",
"#B09AFF",
"#5B4FD6",
"#65BFFF",
"#2F7BE0",
"#6EF59A",
"#4CC46A",
"#FFD84D",
"#FF9F43",
"#E5484D"
] | 16 colours as a JSON array of #rrggbb. |
color | number | 7 | Initially selected palette index. |
local | boolean | false | Paint the local buffer directly (offline mode). Otherwise painted cells stay pending until cells echoes them. |
readonly | boolean | false | Watch only: no painting, no palette. |
grid | boolean | false | Show faint cell lines. |
Events
| Name | Description |
|---|---|
sb-paint | Painted cells, batched every ~80 ms during a stroke. detail: { color, cells: [index…] }. |