Grammar
Every keyword and where it may appear, the shapes of declarations, statements and expressions, operators and precedence, and the literals.
A compact map of what the parser accepts. The chapters say what each form means; this page says where it may be written.
Words
No word is reserved. page, state, type and the rest are keywords only where a statement starts, so every one of them is also usable as a map key ({ action: "approve", type: "a" }), a field name or a parameter. Declarations are lowercase; elements and the names declarations introduce are capitalised; everything else is camelCase.
Keywords
Where a statement starts, these words have meaning: page, component, store, app, theme, type, enum, api, external, const, data, image, animation and test at the top of a file; state, persist, derived, effect, cleanup, action, use, resource, validate, socket, stream, channel, peer, every, after, on key, head, if, if let, for, show, match, sequence and step in a render block; slot, event, part and children in a component; let, return, try, catch, await and emit in an action; expect, click, type, press in a test. Everywhere else they are ordinary names.
Declarations
At the top of a file, and nowhere else:
text
page Name(path: "/", title: …, description: …, image: …, type: …, noindex: …,
layout: Component(…), guard: expr, redirect: "/…", paths: expr,
param: Type, …) { render block }
component Name(_ positional: Type, prop: Type = default, …) { component body }
store Name(scope: .app | .session | .route, eager: Bool) { store body }
app { render block }
theme Name { token: value … }
type Name { field: Type = default, … } type Name = Other { … }
enum Name { case, case(part: Type, …), … }
api Name(base: expr, …) { settings headers { … } on request|response|error(x) { … } endpoints }
api Name from "spec.json" (base: expr)
external Name from "module" { integrity: "…" fn f(params) -> Type type T { method(params) -> Type field: Type } }
external element Name("tag-name") { prop name: Type event name(params) }
const NAME: Type = expr
data name: Type = "file.json"
image name = "picture.jpg"
animation Name { from { css } 50% { css } to { css } }
test "name"(data: { … }) { render block and test steps }
A /// comment before a declaration, a prop, a field, an event or an endpoint is its documentation.
Render blocks
A page, a component, the app, a test and every element's children:
| Statement | Form |
|---|---|
| state |
|
| persisted state |
|
| derived |
|
| effect |
|
| action |
|
| store |
|
| resource |
|
| validation |
|
| connection |
|
| timer |
|
| key |
|
| head |
|
| conditional |
|
| loop |
|
| visibility |
|
| choice |
|
| orchestration |
|
| set-up |
|
| element |
|
| slot use |
|
Component bodies
A component's body is a render block, plus:
text
slot the default slot, placed with `children`
slot name a named slot
slot name(value: Type, …) a scoped slot
event name(param: Type, …) an event it fires with `emit`
part Name(props) { render block } a part, called as `Component.Name`
Element blocks
In this order, each optional:
text
Name(…).flags {
style { declarations }
transition { property: duration easing … }
on event(e) { imperative }
slotName(params) { render block } a slot fill, on a component call
children
}
Imperative blocks
Actions, handlers, effects, timers and hooks:
| Statement | Form |
|---|---|
| assignment |
|
| local |
|
| call |
|
| emit |
|
| return |
|
| conditional |
|
| loop |
|
| error handling |
|
| waiting |
|
Style blocks and themes
text
style {
property: raw css value to the end of the line, a `;`, or two spaces and the next declaration
property: {expr}px $token a live value, a design token
--custom-property: value
&:hover { declarations } nested rules, as CSS nesting writes them
@media (…) { declarations } and `@md { }` for a breakpoint, `@container (…) { }`
}
A theme's body is the same declarations, without nesting.
Test steps
In a test, beside render statements: expect "text", expect not "text", click "name", type "text" into "name", press "Key", press "Key" in "name".
Expressions
From weakest to strongest binding:
| Level | Operators |
|---|---|
| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
unary |
| 10 |
calls |
So 1..n + 1 is 1..(n + 1), and a ?? b || c is a ?? (b || c).
Values that are expressions: if c { a } else { b }, if let x = v { a } else { b }, match v { .case { a } else { b } }, lambdas x => expr, (a, b) => expr, () => expr, spreads ...x in a list or map.
Literals
| Kind | Written |
|---|---|
| String |
|
| Number |
|
| Bool, null |
|
| List, map |
|
| Regex |
|
| Date, time |
|
| Duration |
|
| Money |
|
| Colour |
|
| Enum case |
|
| Design token |
|
Comments
// to the end of the line, /* across lines */, and /// documentation.
Layout
A .wf file writes blocks in braces. A .wfx file writes them by indentation: a line indented deeper than the one before opens a block, a dedent closes it, and braces written by hand still work inside a line (chapter 5). Statements need no separator; on one line, a ; or two spaces reads best.
On this page
Words Keywords Declarations Render blocks Component bodies Element blocks Imperative blocks Style blocks and themes Test steps Expressions Literals Comments LayoutChecked by the test suite
Every code block in the guide is parsed, checked and type-checked on each release.