Reactive Markdown
This page opts into the runtime because it declares state. Static pages ship zero framework JavaScript.
The count is 0.
:if reads JavaScript-style comparisons — == != < <= > >= contains — joined with and, or, and not. This chain reacts to the counter above, no handler wired by hand:
Counter is in the sweet spot (3–5).
Counter is over the top.
Counter is warming up.
Counter is warming up.
Counter is over the top.
Counter is warming up.
Counter is warming up.
The details are visible now.
The details are hidden.
The details are hidden.
Keyed loops over real objects
The same @loop that unrolls static data also drives reactive lists. Items are objects, bindings use dotted paths, and the runtime patches the list by key instead of re-rendering it:
- Route pages — PM
- Ship tiny runtime — Dev
Multi-branch conditionals + reactive classes
:if … :else if … :else … :endif picks one of several branches per row, and a container class can react to the row too: .class when <predicate> toggles a class from the same whitelist @loop … where uses. Each card below picks one of three badges and tags itself .is-done / .is-blocked, all reactive:
—
done ✓
blocked ✗
in progress …
Draft the spec —
done ✓
blocked ✗
in progress …
done ✓
Wire the runtime —
done ✓
blocked ✗
in progress …
blocked ✗
in progress …
blocked ✗
Write the demo —
done ✓
blocked ✗
in progress …
blocked ✗
in progress …
in progress …
Each row picks one of three states from a single :else if chain, and its left border color comes from a reactive .class when … — plain Markdown, no component, no ternary, no JavaScript.
Live filtering — search in pure Markdown
@loop … where filters a list, and because the predicate reads a :state value, the loop re-filters live as you type. :bind is the input bound two-way to that state. Type below and watch the list narrow — no event handlers, no JavaScript:
— $
Aurora Lamp — $49
Briza Fan — $39
Cove Speaker — $89
Dune Mug — $12
Ember Kettle — $64
The predicate whitelist is compile-time validated — item fields, declared state, numbers, and strings only. An item-only predicate (say where p.price < 50) would instead filter at build time and ship zero JavaScript.
…and an effect that watches the search
:effect <state> -> <actions> runs actions whenever the watched state changes — for side effects past deriving state. This one counts how many times the search box above has changed:
The search has changed 0 time(s). Type in the box above and this updates on its own — no handler wired by hand.
…and an editable cart
A :button inside a loop can act on its own row. Add to cart carries the row into another list; Remove drops a line — a whole add/remove flow with no JavaScript:
— $
Section-scoped state
Two sections can each own a state value named tally without colliding. Buttons resolve to the nearest scope:
Left section
The left tally is 0.
Right section
The right tally is 10.
Static pages like the docs stay runtime-free.