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.

$ bun add @joeyguerra/rdbl
~4KB gzipped
0 dependencies
1 file
0 build steps

Live Telemetry

This demo is running rdbl right now. Three signals, one computed, two handlers, eight HTML attributes.

Live Telemetry
0
Mission Count
Double
0
Status
STANDBY
Parity
EVEN
index.html
<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>
app.js
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.

Concept 01 — Signals are the state

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'
Concept 02 — Computed derives state

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'
Concept 03 — bind() wires state to HTML

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

01 HTML is structure Directives describe what to bind, not how. No logic in markup.
02 No expressions in markup Paths only. text="user.name", never {{ user.name }}. Logic lives in JS.
03 Signals are the model One reactive primitive. No magic objects, no proxy traps, no surprises.
04 DOM-first No virtual DOM. Direct updates batched via microtask. Fast and predictable.
05 Single file Copy it, own it. No build pipeline, no peer dependencies, no lock-in.

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)
Ready to go?

The Flight Manual has every directive, API, and pattern with live demos.
The Simulations show complete working apps.

Read the Flight Manual Launch Simulations