One binary · one language
A language for websites.
A file describes what a page shows — its elements, the state behind them, what happens when the reader acts. The compiler turns it into a static site, a single-page app, a PDF or a slide deck, with the accessibility, search and performance work done for you.
$
curl -sSL https://raw.githubusercontent.com/monzeromer-lab/WebFluent/master/install.sh | bash
Linux · macOS · Windows
No Node, no bundler, no config
src/pages/Home.wf
wf serve · localhost:3000
page Home(path: "/", title: "Hello") {
state count = 0
Container {
Heading("Hello, world").h1
Text("You have clicked {count} times.")
Button("Click me").primary {
on click { count = count + 1 }
}
}
}
✓ build succeeded — 1 page, 3 components
watching src/ — reload on save
The five ideas
Everything else follows from these
01
A file is a list of declarations
Fourteen kinds — page, component, store, theme, app, type, enum, api, external, const, data, image, animation, test. Pages own their routes; there is no separate route table.
02
An element is a typed call
One positional argument, named props, flags, then a block. The compiler knows every built-in, so a misspelling is an error with a hint — never a silent no-op.
Button("Save", tone: .primary).lg
03
State is a signal
state count = 0 — anything that reads it follows it. Derived values recompute, effects re-run, the DOM updates. No dependency arrays.
04
Styles are CSS, written where the element is
Raw values, design tokens with $, nested rules as CSS nesting spells them. One sheet, pruned to what you used.
05
The build is the whole site
HTML pre-rendered per page, a small runtime, a sitemap, Open Graph tags, content-security headers — from wf build, with nothing to configure.
Coming from React, Vue or Svelte?
Start with the map from what you know: state is a signal, components are declared and typed, stores replace context, and the compiler checks what you write against what the components declare.
Batteries included
What a real site needs, in the language
A service, described once
api Backend { get users() -> [User] } — every call typed, cached, retried and cancellable; or read the whole surface from an OpenAPI file.
Forms that validate themselves
validate email { required email } beside the state; the field shows its message accessibly, and a failed submit focuses the problem.
Sockets, streams and peers
WebSockets that reconnect, server-sent events, a channel every tab hears, and WebRTC straight to another page — each closed when its page leaves.
Works offline
Name offline in the config: a service worker stores the site, writes wait for the connection, and a new version is offered, not forced.
Tests that click
click "Save" then expect "Saved" runs in headless Chrome, and wf verify loads every page of the build.
Safe by construction
Text is always text, no inline handlers, no javascript: URLs, private env names kept out of the bundle, and a CSP the build holds itself to.
Your own JavaScript, by name
A plain .js file under src/ is linked as written, and its functions are called straight from .wf, checked against their JSDoc.
A template engine for Rust and Node
Render a template with your own data to HTML or a PDF — Template::from_dir("templates") with any Serialize struct, or npm install webfluent.
Outputs
One source, four things to ship
build.output_type in webfluent.app.json chooses what wf build writes.
Single-page app
"spa"
One index.html plus a router that paints pages in the browser. One chunk per page, fetched when its route first shows.
Static site
"spa" + ssg
One pre-rendered HTML file per route, hydrated by the same script. Sitemap, robots and .gz copies beside every text file.
PDF document
"pdf"
A page holds a Document with headers, footers, sections and page breaks. A4 or Letter, margins in points.
Slide deck
"slides"
A Presentation of Slides, written like any other page, rendered one slide per PDF page.
Two layouts, one grammar
Braces or indentation — your call
Both compile to the same output, are read by every tool, and a project may mix them. wf fmt --to wfx converts a project one way and --to wf the other, losslessly.
wf
braces
page Home(path: "/") {
state open = true
Row(gap: .sm) {
style {
padding: 6px 0
&:hover { background: $surface-hover }
}
on click { open = !open }
Text("a").bold
}
if open {
Spinner
} else {
Text("b")
}
}
wfx
indentation
page Home(path: "/")
state open = true
Row(gap: .sm)
style
padding: 6px 0
&:hover
background: $surface-hover
on click { open = !open }
Text("a").bold
if open
Spinner
else
Text("b")
What a build produces
Nothing in build/ is hand-edited
It is regenerated on every build: the page pre-rendered, one directory per route, each page in its own chunk, and a shared sheet carrying your tokens, the built-ins you reached and your stylesheets.
A strict content-security policy, satisfied by the output
Open Graph, Twitter cards and JSON-LD from each page's title
Pre-compressed copies a host can serve as-is
wf build
build/
index.html # the page, pre-rendered
about/index.html # one directory per route
app.js # the runtime, stores, components
pages/Home.js # loaded when its route shows
pages/Home.css # the styles that page reaches
styles.css # tokens + built-ins you used
sitemap.xml, robots.txt
*.gz # pre-compressed copies
Install it and read chapter one
wf init hello writes a small application — four pages, three components and two stores — so you have something real to read, and the tutorial builds a site from nothing to deployed.
bash
Templates: -t spa · -t static · -t pdf · -t slides