Data, forms, and persistence

This page demos the Tier 1.5 surface: declarative fetch, forms that write state, persistent state, and the JavaScript escape hatch.

Fetched data drives reactive loops

:fetch team from "/__wd/data/team.json" declares state and fills it from the network. Until it lands, the page shows the fallback branch:

Loading the team…

That loop also proves :if over loop items: the lead badge is decided per row.

The four states of a fetch

Every :fetch tracks the whole lifecycle — loading, error, empty, and data — so a page can answer each one. Here the same primitive shows an error (a missing URL 404s) and an empty result (a file that is just []):

Waiting…

The error branch uses an :else if chain; the empty branch uses the loop's @empty. No state machine to wire by hand.

Authenticated requests

A real API usually wants a token. :fetch … headers=<key> spreads a state object into the request headers, so pair it with :store to persist that token across reloads:

Loading the authenticated feed…

This demo points from= at a static file (which ignores the header), so it stays self-contained — point it at your real API and the bearer token travels with every request. When that token expires, add refresh="/auth/refresh" and Darkmown renews it on a 401 and retries once, automatically.

Forms write state, no backend required

:form into profile captures the submit straight into state. With action="/url" instead, Darkmown emits a plain native form and stays out of the way entirely:

Submit the form and this sentence reacts. Nothing leaves the page.

Server round-trips, adapter style

Darkmown does not own your backend — point a form at any endpoint. With JS, the submit becomes a fetch and the JSON reply lands in state; without JS, the same markup is a plain native POST:

The reply will appear here.

In darkmown dev the echo endpoint is built into the dev server; on darkmown.com it is a real serverless function behind the same URL. On static-only hosts such as a plain Cloudflare Pages deploy, replace action="/__wd/echo" with your own API endpoint. Either way, Darkmown itself stays static — sessions ride on :fetch plus ordinary cookies against your real API.

Persistent state survives reload

:state items = [] persist keeps this section in localStorage. Add a few, reload the page, and the count holds:

The cart holds 0 item(s) worth $0.

:computed total = items.length * 4 derives state from state — the price updates with every cart change, persistence included.

The escape hatch

Colocated data.js uses the window.wd API (wd.get, wd.set) for anything directives can't say. Section state is addressed as cart:items:

Lazy loading

The list below uses when=visible — the request only fires once this section scrolls into view. Watch the network tab:

Scroll me into view and I will load.

Richer form controls

:textarea, :select, :checkbox, and :radio join :input, all captured into the same :form into state with no backend. A :checkbox group captures every checked value as an array; a :radio group captures a single value:

Pick a topic and send — it is captured client-side, no backend.

Dynamic links from data, and link buttons

A build-time @loop can drive the href itself — [{ link.label }]({ link.url }) resolves the destination at compile time, so these stay plain static links:

A trailing {.class} styles a single link as a button — no wrapper container:

Read the docs