One binary in, a website out. Write .wf, get HTML, CSS and JavaScript — no framework, no node_modules, no build config. The same source also compiles to PDF documents and slide decks.
Docs · Tutorial · Components · Guide source
store Todos {
state items = []
action add(title: String) {
items = items.concat([{ id: uuid(), title: title, done: false }])
}
}
page Home(path: "/", title: "Todos") {
use Todos
state draft = ""
Container {
Heading("Todos").h1
Row(gap: .sm) {
Input(bind: draft, placeholder: "What needs doing?").text
Button("Add").primary { on click { Todos.add(draft) draft = "" } }
}
for t in Todos.items by t.id {
Card { Text(t.title) }
}
}
}
app { Router }
That is the whole app — routing, reactivity, styling and the components included.
macOS and Linux — the binary for your platform, no Rust needed:
curl -sSL https://raw.githubusercontent.com/monzeromer-lab/WebFluent/master/install.sh | bashWindows: irm https://raw.githubusercontent.com/monzeromer-lab/WebFluent/master/install.ps1 | iex · With Rust (everywhere Rust runs, and the way to install on Linux arm64): cargo install webfluent · A .deb, .msi and tarballs are attached to each release.
wf --version
wf init my-app # -t spa (default) · static · pdf · slides
cd my-app
wf serve # http://localhost:3000, rebuilds on savewf build writes the finished site to build/ — static files any host serves. Deploying has GitHub Pages, Netlify, Vercel, Cloudflare and nginx.
If the shell cannot find wf, open a new terminal — the installer adds its directory to your PATH in your shell's startup file. WF_INSTALL_DIR installs elsewhere, WF_VERSION pins a release.
- Reactivity without a framework — signals update the exact DOM node that changed. No virtual DOM, no diffing.
- 50+ components, already designed — layout, navigation, tables, forms, modals, media. Styled by design tokens you can retheme in ten lines.
- A compiler that actually checks your work — types, props, flags, slots and events, plus lints for accessibility, SEO and dead code. A typo is an error with a line number, never a silent no-op.
- Ships small — the build keeps only the runtime modules your program reaches, splits a chunk per page, and precompresses everything. A page of static text ships under 4 kB of script, gzipped.
- Four targets, one language — SPA, pre-rendered static site, PDF, or a slide deck, by one config flag.
- Batteries included — routing, stores, forms and validation,
fetchas a typedapi, i18n with automatic RTL, animations, dark mode. - Editor support — a language server for Zed and VS Code: completion, hover, go to definition, rename, quick fixes.
Components, props and flags
/// A person, at a glance.
component UserCard(_ name: String, role: String, active: Bool = true) {
Card.elevated {
Row(align: .center, gap: .md) {
Avatar(initials: "U").primary
Stack {
Text(name).bold
Text(role).muted
}
if active { Badge("Active").success }
}
}
}
UserCard("Monzer", role: "Developer")
A .flag sets a boolean prop or picks an enum case. A flag the component does not declare is a compile error, with the ones it does take listed.
Data fetching
resource users = fetch("/api/users")
match users {
loading { Spinner }
error(err) { Alert("Failed to load").danger }
ready(users) { for u in users by u.id { UserCard(u.name, role: u.role) } }
}
Describe a whole service once with api, and every call site is typed, cached, retried and cancellable.
Styling
Button("Save").primary.lg.rounded
Button("Custom") {
style {
background: $surface
padding: $xl
&:hover { background: $primary }
@md { padding: $lg }
}
}
$token is a design token, {expr} reads state. Any .css file under src/ is bundled too.
Braces or indentation
A .wf file writes blocks in braces; a .wfx file writes them by indentation. Same grammar, same output — wf fmt --to wfx switches a project either way.
page Home(path: "/", title: "Home")
state count = 0
Container
Heading("Welcome").h1
Button("+1").primary
on click
count = count + 1
PDFs and slides
page Deck(path: "/", title: "Q1 Review") {
Presentation {
TitleSlide("Q1 Review", subtitle: "March 2026")
Slide {
Heading("Highlights").h1
List { Text("Revenue grew 15%") Text("Expanded to 5 markets") }
}
SectionSlide("Q2 Plan").primary
}
}
Set "output_type": "slides" (or "pdf") and wf build writes the PDF. Raw PDF 1.7 bytes, no external dependencies — and interactive components are rejected at compile time rather than silently dropped.
wf init <name> [-t spa|static|pdf|slides] Create a project
wf build [-d DIR] [--stats] Compile; --stats prints what it weighs
wf serve [-d DIR] Dev server with live reload
wf generate page|component|store <name> Scaffold a file
wf fmt [path] [--check] [--to wfx|wf] Format, or switch layout
wf test [path] [--update] Run the project's tests, in a browser when one acts
wf verify [path] [--json] [--budget MS] Visit every route in a headless browser
wf docs [-d DIR] [-o OUT] Write a component gallery
wf render <tpl> [--data f.json] [-f pdf] Use WebFluent as a template engine
wf audit [path] [--json] What the project trusts
wf registry|types [--json] The registry, and what a project declares
wf migrate [path] [--check] [--wfx] WebFluent 2 or 3 → 4
- The documentation — built with WebFluent itself: getting started, a tutorial that ends in a deployed site, the language, and deploying
- Reference — every component, CLI command, config key, diagnostic and keyword
- Troubleshooting and upgrading
- The guide's source — the same chapters as Markdown; every code block in them is compiled by the test suite
.wf source → Lexer → Parser → Type checker → Linters → Code generator → HTML + CSS + JS
→ PDF validator → PDF 1.7
The compiler is Rust. The generated JavaScript is a small signal-based runtime with no dependencies. PDF output is written byte by byte with Base14 font metrics.
To build the compiler from a clone — for an unreleased change, or to work on it:
git clone https://github.com/monzeromer-lab/WebFluent.git
cd WebFluent && cargo install --path .Contributions welcome — see the spec for what the language promises.