Markdown that runs.
A .md file is already a page. Rename it to .wd and that same file gets loops, state, fetched data, and forms. No components, no JSX, no config. Pages with no state ship zero framework JavaScript.
npx @zvndev/darkmown init my-site
One file. Here is the source.
A store with a live search filter, add-to-cart, and a running total.
store.wd, the whole file
:state products = [
{ "id": 1, "name": "Aurora Lamp", "price": 49 },
{ "id": 2, "name": "Briza Fan", "price": 39 },
{ "id": 3, "name": "Cove Speaker", "price": 89 },
{ "id": 4, "name": "Dune Mug", "price": 12 }
]
:state cart = []
:state q = ""
:computed count = cart.length
:bind q placeholder="Search products…"
@loop products into p where p.name contains q
::: card .product
**{ p.name }** · ${ p.price }
:button "Add" -> cart += p
:::
@endloop
Cart: { count } item(s)
that same file, running (type in the box)
· $
Aurora Lamp · $49
Briza Fan · $39
Cove Speaker · $89
Dune Mug · $12
Cart: 0 item(s)
This page is built in Darkmown
Not a screenshot, and not a React site describing a Markdown tool. site/pages/index.wd is a .wd file with no raw HTML in it. The first two numbers below are live: a :fetch reading api/stats.js, a serverless function in this same repo.
·
n/a
latest release, live from npm
·
n/a
npm installs last month
7,654 B
the whole runtime, gzipped
0 KB
JS on a static page
Say hello and the form round-trips to a real serverless function, then renders the reply:
The server answered at . That was a :form action=. With JavaScript off it degrades to a plain native POST.
The echo endpoint is only live on the deployed site:
The echo endpoint is only live on the deployed site:
Three rules, no escape hatch
- Zero-JS static. A page with no state ships no framework JavaScript. Open the docs or a plain
.mdpage and check the network tab. - One runtime. Every reactive page shares the same 7,654-byte gzipped runtime, CI-enforced under 8 KB. No per-component bundles, no hydration tax that grows with your UI. It runs under a strict CSP with no
unsafe-eval. - One loop, one binding.
@loop … into …is the only loop.{ name }is the only interpolation. There is no second way to do either.
A whole framework, in text
- Routing. Folders are routes. Drop a file in, it is a page. Prefix it with
.or-to hide it. - State and reactivity.
:state,:button,:if, and keyed@loops patch the DOM directly. No virtual DOM. - Server calls.
:fetchpulls JSON into state;:form action=round-trips to any backend and degrades to a native POST without JS. - Composition.
@includeembeds one Markdown file into another, passing values down. - Styling, optional. Colocate a
.skinfile for design tokens, or ship none and let the browser default. - Errors that teach. Every compile error carries a stable code, the
file:line, and a corrective example that is guaranteed to compile.
See it for real
TryA whole live app in one file: fetch, loops, nested ifs, a form TryThe docs, a .wd page built from includes TryA plain .md page, where directives stay inert textnpm run build emits a plain static dist/ (no server, no special runtime), so it deploys anywhere that serves files. This site is a Darkmown build.