Coming from React, Vue or Svelte
What you already know, mapped to what is here — and the few places WebFluent works differently.
If you have built with a JavaScript framework, most of WebFluent will feel familiar: components with props, reactive state, a router, a store. This chapter maps those ideas across, then lists what is genuinely different.
The map
| You know | In WebFluent | Chapter |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| a component function / SFC |
|
|
| props |
typed props: |
|
|
|
|
|
| named slots, render props |
|
|
|
callback props, |
|
|
|
|
|
|
| context, provide/inject, a global store |
|
|
| Redux / Pinia / Zustand |
a |
|
| React Router / Vue Router / SvelteKit routes |
|
|
| layouts |
|
|
| route guards |
|
|
|
|
|
|
|
|
|
|
| TanStack Query / SWR |
|
|
| TypeScript | a built-in, gradual type checker; types are optional | |
| CSS modules / scoped styles |
|
|
| Tailwind utility classes |
flags ( |
|
| Framer Motion / transitions |
|
|
| react-i18next / vue-i18n |
|
|
| Next.js static export / Astro |
|
|
| Testing Library + Playwright |
|
|
| ESLint a11y plugin | accessibility checks on every build |
The same component, three ways
React:
jsx
function Counter({ start = 0, onChange }) {
const [n, setN] = useState(start);
const even = useMemo(() => n % 2 === 0, [n]);
return (
<div className="row">
<button onClick={() => { setN(n + 1); onChange?.(n + 1); }}>+</button>
<span>{n}</span>
{even && <span className="badge">even</span>}
</div>
);
}
WebFluent:
wf
component Counter(start: Number = 0) {
event change(value: Number)
state n = start
derived even = n % 2 == 0
Row(gap: .sm, align: .center) {
Button("+") { on click { n = n + 1 emit change(n) } }
Text("{n}")
if even { Badge("even").info }
}
}
What changed: no hooks and no dependency arrays — derived knows what it read. No setter — you assign. No className — Badge is designed, and .info is a flag the compiler checks. The event is declared, so a caller handling on chnage is an error, not a handler that never runs.
What is genuinely different
It is a compiler, not a library. There is no virtual DOM and no re-render of a component. Each text, attribute or style that reads a state subscribes to it and updates alone. A component's body runs once, when it is placed.
The markup is typed. A built-in's props, flags, events and slots are known to the compiler, and so are yours. Button("Save").primray does not build. There is no "any prop goes through to the DOM" — an unknown named argument becomes an HTML attribute with a warning, unless it is aria-*, data-* or a global attribute.
Pages own their routes. There is no route table or file-system routing: each page names its path: and the router is built from them, ordered by specificity.
No imports. Every file under src/ is one program. A component declared anywhere is usable everywhere; two declarations with one name are an error.
Styling lives on the element. A style { } block is real CSS scoped to that element; $token reads a design token. There is no separate CSS-in-JS runtime — static rules are compiled into the stylesheet.
The build is the site. Pre-rendering, code splitting, precompression, image resizing, a sitemap, Open Graph, a CSP — all from wf build, with no plugins to choose.
Safe by construction. Text is always text; the only way to inject markup is Unsafe.Html(…), which the build flags every time. Inline handlers and javascript: URLs do not compile. See Security.
Things you might look for
- npm packages. There is no package manager for WebFluent code. A JavaScript library is used through
external— a typed declaration of what you call — andHost, which gives it a DOM node with a lifetime. JavaScript interop shows a chart library end to end. - Server components / SSR per request. Not here: a WebFluent site is static files plus a browser runtime, served by any host, talking to your API. For HTML generated on a server — emails, invoices, fragments — see Server rendering.
- Using WebFluent inside an existing React or Vue app. Publish components as custom elements and place the tags: Custom elements.
- Coming from HTMX. The closest thing to
hx-getis aresourcewhose URL reads state, and tohx-postan action thatawaits anapiendpoint. The page stays static HTML until its script arrives, then updates in place.
On this page
The map The same component, three ways What is genuinely different Things you might look forChecked by the test suite
Every code block in the guide is parsed, checked and type-checked on each release.