Flight Mission 1.0 — Cleared for Launch
rdbl
Signals. Plain HTML. No build step.
Reactive DOM binding the way it should be.
Most reactive libraries ask you to learn a new template language or move your logic into your markup. rdbl goes the other way: your HTML stays plain and readable, your JS stays in JS. Wire them together with attributes, not incantations.
Live Telemetry
This demo is running rdbl right now. Three signals, one computed, two handlers, eight HTML attributes.
<div id="hero-demo">
<div class="counter-value" text="count"></div>
<div class="readout-value" text="double"></div>
<div class="readout-value" text="status" cls="statusCls"></div>
<div class="readout-value" text="parity"></div>
<button onclick="decrement">−</button>
<button onclick="reset">RST</button>
<button onclick="increment">+</button>
</div>
import { bind, signal, computed } from 'rdbl'
const count = signal(0)
const double = computed(() => count() * 2)
const status = computed(() => count() > 0 ? 'NOMINAL' : count() < 0 ? 'ABORT' : 'STANDBY')
const statusCls = computed(() => count() > 0 ? 'green' : count() < 0 ? 'red' : 'amber')
const parity = computed(() => count() % 2 === 0 ? 'EVEN' : 'ODD')
bind(document.querySelector('#hero-demo'), {
count, double, status, statusCls, parity,
increment: () => count.set(count() + 1),
decrement: () => count.set(count() - 1),
reset: () => count.set(0),
})
How It Works
Three ideas. That's it. Learn these and you know rdbl.
A signal holds a reactive value. Read it by calling it. Write it with .set(). Everything downstream updates automatically.
const name = signal('Houston')
name() // → 'Houston'
name.set('Apollo')
name() // → 'Apollo'
A computed is lazy derived state. It re-evaluates only when its dependencies change. No watcher setup, no manual subscriptions.
const greeting = computed(() => `Hello, ${name()}`)
greeting() // → 'Hello, Apollo'
name.set('Discovery')
greeting() // → 'Hello, Discovery'
bind() reads plain HTML attributes and connects them to your state. No special syntax — just dot paths pointing into your scope.
<!-- HTML -->
<h1 text="greeting"></h1>
<input model="name">
// JS
bind(document.querySelector('#app'), { name, greeting })
Directives at a Glance
Eight HTML attributes. That's the entire binding vocabulary.
| Attribute | What it does | Example |
|---|---|---|
text="path" |
Sets textContent |
<span text="user.name"> |
html="path" |
Sets innerHTML |
<div html="article.body"> |
show="path" |
Toggles visibility (hidden attr / dialog) | <dialog show="isOpen"> |
cls="path" |
Sets className (string) or toggles (object) | <div cls="statusClass"> |
attr="n:path; n2:p2" |
Sets arbitrary attributes | <button attr="aria-label:label"> |
model="path" |
Two-way binding (input, select, checkbox…) | <input model="query"> |
each="path" key="id" |
Keyed list rendering with <template> |
<ul each="todos" key="id"> |
on<event>="path" |
Event binding to handler function | <button onclick="addTodo"> |
Design Principles
text="user.name", never {{ user.name }}. Logic lives in JS.
Full Example
The complete todo app from the README — signal, computed, event handlers, list rendering, and two-way input binding. All of rdbl in ~30 lines.
<div id="app">
<h1 text="title"></h1>
<input model="newTodo" placeholder="Add a task...">
<button onclick="addTodo">Add</button>
<ul each="todos" key="id">
<template>
<li>
<input type="checkbox" model="done">
<span text="text" cls="itemCls"></span>
<button onclick="remove">✗</button>
</li>
</template>
</ul>
<p text="summary"></p>
</div>
import { bind, signal, computed, getItemContext } from 'rdbl'
const todos = signal([
{ id: 1, text: 'Read the docs', done: false, itemCls: '' },
{ id: 2, text: 'Build something', done: false, itemCls: '' },
])
const newTodo = signal('')
const state = {
title: 'My Todos',
todos,
newTodo,
summary: computed(() => {
const all = todos()
const done = all.filter(t => t.done).length
return `${done} of ${all.length} done`
}),
addTodo() {
const text = newTodo().trim()
if (!text) return
todos.set([...todos(), { id: Date.now(), text, done: false, itemCls: '' }])
newTodo.set('')
},
remove(event, el) {
const { item } = getItemContext(el)
todos.set(todos().filter(t => t.id !== item.id))
}
}
bind(document.querySelector('#app'), state)
The Flight Manual has every directive, API, and pattern with live demos.
The Simulations show complete working apps.