Showcase

Real apps launched with community components. Yours could be next.

Mission Control

PAD T−8.0 s
Altitude
How it works

The dashboard is built only from community components: sb-starfield, sb-voxel, sb-gauge, sb-sparkline and sb-meter. It contains no custom JavaScript. The server pushes data, signals carry it, and each component watches its attributes.

1. The server streams signals

The section opens one long-lived request with @get('/demo/telemetry'). Four times a second the server sends a datastar-patch-signals event that merges fresh numbers into a single signal, $_tm. The data is a pure function of the clock over a 90-second mission, so the server keeps no state and every viewer watches the same flight. In CQRS terms this is a query stream: nothing is written, and hidden tabs close it and reopen it when they come back.

// internal/web/demo.go: a query stream, no state, no database.
func (s *Server) demoTelemetry(w http.ResponseWriter, r *http.Request) {
	sse := datastar.NewSSE(w, r)
	tick := time.NewTicker(250 * time.Millisecond)
	for {
		// A pure function of the clock: every viewer sees the same flight.
		sse.MarshalAndPatchSignals(map[string]any{"_tm": TelemetryAt(time.Now())})
		select {
		case <-tick.C:
		case <-r.Context().Done():
			return
		}
	}
}

2. Signals drive attributes

Each component is wired with data-attr. When $_tm changes, Datastar updates exactly the attributes that depend on it. Expressions can reshape the data on the way: the starfield's speed is derived from velocity, and warp switches on above 5 km/s.

<section data-ignore-morph
  data-signals="{_tm: {alt: 0, vel: 0, fuel: 100, temp: 18, pitch: 90}}"
  data-init="@get('/demo/telemetry')">

  <sb-starfield data-attr:speed="Math.round(3 + $_tm.vel * 11)"
                data-attr:warp="$_tm.vel > 5"></sb-starfield>
  <sb-gauge label="Velocity" unit=" km/s" max="8" decimals="2"
            data-attr:value="$_tm.vel"></sb-gauge>
  <sb-sparkline data-attr:value="$_tm.alt" length="80" show-value></sb-sparkline>
  <sb-meter label="Fuel" warn="30" danger="15"
            data-attr:value="Math.round($_tm.fuel)"></sb-meter>
</section>

The section is a data-ignore-morph island. The page's own render stream may re-render everything around it, but it never touches these attributes, because signals own them.

3. Components react to their attributes

  • Gauge: the new value becomes a spring target, and the needle eases toward it with a small overshoot, like hardware.
  • Sparkline: in push mode every change of value appends a point. The line fills its width, then scrolls.
  • Starfield and voxel: canvases that repaint only while something changes, pause offscreen, and respect prefers-reduced-motion.
  • Meter: re-renders its segments and turns warn or danger at its thresholds (fuel counts down, so low is bad).

Pixel Board

1 watching · 1307 pixels painted

Paint together. Everyone on this page shares one 48×48 board. Pick a colour and drag.

Faded pixels are still in flight. About 20 pixels per second each, so everybody gets a turn.

How it works

This is the "one billion checkboxes" idea from Anders Murphy's hyperlith, done the way this whole site works: CQRS over one SQLite database, and the server re-rendering the entire page for every change.

1. The board is one attribute

The state of all 2,304 pixels is one string on the element: cells, a hex digit (palette index) per pixel, row by row. The server renders it like any other markup. When a new frame morphs in, the attribute changes and the component repaints its canvas. The component never owns the board. It only draws what the server says, plus your own pixels in flight, drawn faded.

<!-- rendered by the server into every frame of the page's stream -->
<sb-pixel-board id="board" size="48" cells="0000…5ff5…0d00"
  data-on:sb-paint="@post('/cmd/paint', {
    payload: {color: evt.detail.color, cells: evt.detail.cells},
    requestCancellation: 'disabled'
  })"></sb-pixel-board>

2. Painting is a command

A stroke emits sb-paint every 80 ms, with the cells it crossed; fast drags are interpolated so there are no gaps. Each batch is a short POST that answers 204 No Content and returns no HTML. requestCancellation: 'disabled' matters here: by default Datastar cancels an in-flight request when a new one goes to the same URL, which would drop pixels mid-stroke. A per-session token bucket (20 pixels per second, bursts up to 60) keeps any one painter from flooding the board.

// internal/commands/board.go: runs inside the single writer's batch.
func (c PaintPixels) Apply(ctx context.Context, tx *sql.Tx) error {
	for _, i := range c.Cells {
		tx.ExecContext(ctx, `INSERT INTO board_cells (board, idx, color, painted_at)
			VALUES (?, ?, ?, ?) ON CONFLICT (board, idx) DO UPDATE
			SET color = excluded.color, painted_at = excluded.painted_at`,
			c.Board, i, c.Color, now)
	}
	// bump the version: every open stream re-renders once the batch commits
	...
}

A single writer goroutine drains the command queue. Everyone's strokes that arrive together are applied in one SQLite transaction, each in its own savepoint, and committed once.

3. Every tab re-renders the whole page

After the commit, the hub wakes every open render stream. Each re-renders its page from a fresh read snapshot and sends the entire page again. The encoded board is cached per version, so a thousand viewers cost one query. Pages whose output didn't change send nothing.

Sending the whole page sounds wasteful until you look at the wire. The stream is compressed with Brotli, and the compressor keeps its window across frames. A full frame of this page is about 9.7 KB; the first one compresses to about 3.4 KB, and after a paint each complete re-render costs about 17 bytes. There is no diffing code anywhere: Datastar morphs the DOM, and compression takes care of the network.

4. Presence

"Watching" counts the open render streams on this page. The hub wakes the other viewers when someone joins or leaves, so the number is live too.

Commands and components

Every control here sends a command. The server validates it, stores it and re-renders the page; until the new value comes back, the control is pending (dashed). The server upper-cases call signs, and it rejects thrust above 90%: the slider goes back.

Thrust
40%
Shields
on
Call sign
STARBASE-1

Your project here

If you've shipped something with these components, open a pull request that adds it to content/showcase.md, with a sentence about what you built, a link and optionally a screenshot.

Tip: the best entries show a component doing something the docs don't: a clever data-bind, a server-driven modal, a theme of your own.