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 warming up.

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:

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:

Draft the spec

done ✓

Wire the runtime

blocked ✗

Write the demo

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.

Static pages like the docs stay runtime-free.