Browse documentation
Documentation

Runtime expressions

Edit this guide on GitHub

The expr! macro defines an expression that can run on the server and in the browser. It compiles to Rust for the initial render and JavaScript for browser updates. Inside view!, write runtime expressions as $(...).

Expressions support a subset of Rust with matching behavior in both languages. The supported types, operations, and syntax are described below.

let show = signal(cx, || false);

Ok(view! {
    $(if show.get() { "shown" } else { "hidden" })
})

The server evaluates the expression for the initial HTML. In the browser, it runs again whenever a signal it reads changes and updates the DOM without a server request.

The macro returns an Expr containing the server result and browser code.

Server evaluation is synchronous. An expression that reads no signals renders as static content. An event-handler closure's body runs only when the browser invokes it.

Captured variables

An identifier that is not defined inside the expression is captured from the surrounding Rust scope:

let count = signal(cx, || 0usize);

Ok(view! {
    $(count.get() + step)
})

Capturing clones the value, so surrounding code can still use it. The browser receives a snapshot from the render. Later server changes do not update that snapshot. Captured values must use the supported types described below. Capturing an owned collection clones its elements. Capturing a slice borrows its elements on the server, but still sends a snapshot to the browser.

A captured Expr<T> behaves as T inside another expression. The server reuses its evaluated value. The browser evaluates its expression at each use, so signal reads remain reactive, including inside event handlers.

let selected = signal(cx, || "overview".to_owned());
let active = expr!(selected.get() == "overview");
let label = expr!(if active { "Selected" } else { "Select" });

The shared vocabulary

Runtime types expose a subset of their Rust APIs:

Operations follow Rust semantics in both languages. For strings, len counts UTF-8 bytes, comparisons use code point order, and trimming uses Unicode White_Space. This means trimming keeps U+FEFF and removes U+0085.

Supported syntax

Expressions use a subset of Rust's syntax:

Unsupported syntax produces a compile error at the expression.

Embedding JavaScript

Use raw! for JavaScript operations outside the supported vocabulary. Its first argument is JavaScript source. The optional second argument is equivalent Rust for server evaluation:

let name = signal(cx, String::new);

Ok(view! {
    $({
        let n = name.get();
        raw!("${n}.toUpperCase()", n.to_uppercase())
    })
})

${ident} in the JavaScript string inserts a binding from the expression's scope. Omitting the Rust argument restricts the expression to browser execution. You are responsible for keeping the Rust and JavaScript behavior equivalent.

Signal reads in the Rust fallback determine whether the expression needs a browser binding. The fallback must therefore read the signals its JavaScript depends on, even if their initial values happen to produce a constant result.