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.