Write @if, @forand @switch in plain HTML. A Rust compiler turns your template into a render(context) function, or into HTML directly. Angular-style authoring, without Angular, and with nothing to adopt around it.
FlowView compiles templates. The host decides when to render, where to serve and how to navigate.
Astro
Embed a <template flowview> region in any.astro file, or import a standalone.flow file. HTML is produced at build or request time. Add FlowView Events for (click) bindings.
With @flowview/vite, a.flow file becomesexport function render(context). Your server decides when to call it, which makes Hono, Node.js, Cloudflare Workers and other Vite-built servers a natural fit.
compile_static() parses once and returns an immutableCompiledStaticTemplate; render it with as many contexts as you like. No JavaScript engine is involved, so templates can only use a small deterministic expression subset: property paths, literals, comparisons and boolean operators.
rust
use flowview_compiler::{compile_static, StaticCompileOptions};
let template = compile_static(source, StaticCompileOptions::default())?;
for page in &pages {
let context = serde_json::to_value(page)?;
let html = template.render(&context)?;
// write html ...
}
Small browser UIs
Compose @flowview/dom,@flowview/reactive and Events when you want one. FlowView does not make templates reactive. Your code owns the state, and an expliciteffect() calls view.update().
ts
import { createView } from "@flowview/dom";
import { effect, signal } from "@flowview/reactive";
import { render } from "./items.flow";
const state = signal({ loading: false, items: [] });
const view = createView("#app", render);
// The one explicit boundary: state -> view.
effect(() => {
view.update(state());
});
Compiler
One parser, two render targets
The same .flow source and the same AST feed a JavaScript backend and a native static HTML backend, and both follow one specification.
.flow or embedded templateHTML + control flow
↓
FlowView compilerone parser, one AST
↓
JavaScript targetrender(context): string
↓
Astro · Vite · HonoNode · Workers · browser
Native Rust targetHTML from a JSON context
↓
Rust · CLIstatic tooling
JavaScript target
For Astro, Vite, Hono, Node and Workers, and for browser code after a build. Source maps point errors back to your.flow file.
Native static target
For Rust tools, documentation generators, reports and server-side Rust applications. Structured diagnostics carry a code, line and column.
FlowView is not a static site generator. Routing, Markdown and deployment stay with your host.
Escaped by default, raw on purpose
{{ value }} is escaped. {{{ value }}} inserts trusted HTML exactly as given and is never sanitized. Templates are trusted source.
Events are a separate, optional compiler. Bindings are plain template syntax. Neither needs a component model.
Events
(click), (input), (change),(submit) and other native events compile to one delegated listener per event type, so DOM you render later keeps working. The Rust HTML compiler never sees (click).
Eventshtml
<button (click)="save()">Save</button>
<input (input)="search($event)" />
<select (change)="change($event)">
<option value="a">A</option>
<option value="b">B</option>
</select>
<form (submit)="submit($event)">
<button>Send</button>
</form>
<button (click)="remove('item-1', $el)">Remove</button>
<script data-flowview>
function save() {}
function search(event) { /* event.target.value */ }
function change(event) {}
function submit(event) { event.preventDefault(); }
function remove(id, element) {}
</script>
State lives in your code. A signal changes, an effect runs, and view.update() replaces the target HTML with a fresh render. There is no virtual DOM and no automatic dependency tracking inside templates.