Simulation 01
Counter

The Hello World of reactive UIs. Three signals, two computeds, three handlers. This is all of rdbl in 20 lines.

Live Telemetry
0
Mission Count
Double
0
Status
STANDBY
History
Effect log
counter.html
<div id="counter-app">

  <!-- Display -->
  <div class="counter-value" text="count"></div>

  <!-- Derived readouts -->
  <div text="double"></div>
  <div text="status" cls="statusCls"></div>

  <!-- Controls -->
  <button onclick="decrement">−</button>
  <button onclick="reset">RST</button>
  <button onclick="increment">+</button>

</div>
counter.js
import { bind, signal, computed, effect } from 'rdbl'

// ── State ──────────────────────────────────────────────
const count = signal(0)

// ── Derived ────────────────────────────────────────────
const double    = computed(() => count() * 2)
const status    = computed(() =>
  count() > 0 ? 'NOMINAL' :
  count() < 0 ? 'ABORT'   : 'STANDBY'
)
const statusCls = computed(() =>
  count() > 0 ? 'readout-value green'  :
  count() < 0 ? 'readout-value red'    :
                 'readout-value amber'
)

// ── Side effect ────────────────────────────────────────
effect(() => {
  // Re-runs whenever count changes
  console.log('count changed to', count())
})

// ── Bind ───────────────────────────────────────────────
bind(document.querySelector('#counter-app'), {
  count,
  double,
  status,
  statusCls,
  increment: () => count.set(count() + 1),
  decrement: () => count.set(count() - 1),
  reset:     () => count.set(0),
})

Walkthrough

1. signal — reactive state

One signal holds the count. count() reads it. count.set(n) writes it and triggers all dependents.

const count = signal(0)
count()        // 0
count.set(5)
count()        // 5

2. computed — derived state

Computed values re-evaluate automatically. No wiring needed — they just call the signals they depend on.

const double = computed(() => count() * 2)
// double() always equals count() × 2
// Re-evaluates lazily when count changes

3. effect — side effects

Runs immediately, re-runs when dependencies change. Return a cleanup function for teardown.

effect(() => {
  console.log('count is', count())
  return () => console.log('cleanup on re-run')
})

4. bind() — wire to DOM

No magic. bind() walks the element tree, finds directive attributes, and creates effects for each one.

bind(root, {
  count,
  double,
  increment: () => count.set(count() + 1),
})
The philosophy There's no framework magic here. bind() is just a convenience that creates effects for you. You could wire everything manually with effect() and it would work identically. rdbl is transparent all the way down.
Next: Todo Mission ▶ Flight Manual