Skip to content

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.

Take the shortcut →

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.

Read the chapter →

Forms that validate themselves

validate email { required email } beside the state; the field shows its message accessibly, and a failed submit focuses the problem.

Read the chapter →

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.

Read the chapter →

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.

Read the chapter →

Tests that click

click "Save" then expect "Saved" runs in headless Chrome, and wf verify loads every page of the build.

Read the chapter →

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.

Read the chapter →

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.

Read the chapter →

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.

Read the chapter →

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