Skip to content
On this page

Component and typed DOM DX in Verniz

Verniz markup is a concise calling surface for ordinary Zolo functions. Components get their props from function signatures, child blocks are typed function values, and native elements are checked against the compiler's versioned DOM table.

Concise attributes

<Row {item} />
<button {onclick}>Save</button>
<input bind:value />

These mean item={item}, a zero-argument DOM handler, and bind:value={value}. The formatter preserves the authored form. Expand/collapse is an explicit editor refactor, and rename expands a shorthand when the prop name and forwarded local name diverge.

Typed child blocks

fn TodoList(
    items: [Todo],
    row: fn(item: Todo) -> View,
    children?: fn() -> View,
) -> View {
    <ul>{for item in items { <row {item} /> }}</ul>
    {children?.()}
}

<TodoList {items}>
    {fn row(item) { <li>{item.title}</li> }}
    <p>{items.len()} items</p>
</TodoList>

The named block lives in the parent's body but binds to its row parameter. Unnamed body content binds to children. Function-type labels are a calling interface and do not change function type identity.

Associated server actions

Server actions can live beside the data they mutate as associated methods:

struct Todo() {
    @action
    fn add(description: str) {
        // database mutation
    }

    fn seed() {
        Self::add("learn Verniz")
    }
}

@island
fn NewTodo() -> View {
    var<signal> description = ""
    <button onclick={ Todo::add(description) }>Add</button>
}

The source keeps Zolo's Todo::add spelling. Verniz registers the qualified wire identity Todo.add, so two types may both declare an add action without colliding. Self::add resolves to the containing type inside its methods. An action method is associated/static; methods that require a self receiver are not remote actions.

Typed native DOM

Completion and hover are contextual to the element:

<input type="checkbox" bind:checked {onchange} aria-label="Done" />
<video bind:currentTime={position} ontimeupdate={sync()} />

Verniz checks known attributes, events, binding value types, void elements, ARIA spelling/roles, image alternatives and high-confidence accessible names. data-* and web-component attributes remain extensible. Hover shows accepted values, event types and the element's MDN link.

Small directives use the existing extras channel:

<button class:active style:color={accent} />
<button onclick:prevent:stop={save()} />

When the directive suffix and its binding have the same name, omit the value: class:active is the idiomatic shorthand for class:active={active}. The same rule applies to style:color and bind:value; keep the explicit form when the value is a different expression.

Spread, forwarding and refs

fn Button(label: str, attrs: HtmlAttrs = #{}) -> View {
    <button {...attrs} class="primary">{label}</button>
}

@island
fn Search() -> View {
    var<signal> field: DomRef = nil
    <input ref={field} aria-label="Search" />
}

Spreads evaluate once from left to right and the rightmost attribute wins. Dynamic HTML forwarding requires HtmlAttrs. Component spreads require statically known record fields and still use ordinary named-argument checking. A ref is nil during SSR, receives the real element during attach, and returns to nil when detached.

Dynamic components, portals and boundaries

let Current: fn(item: Todo) -> View = compact if dense else detailed
<Current {item} />

portal(target: "#modal") { <Dialog /> }

<Boundary task={load_user()}>
    {fn pending() { <Spinner /> }}
    {fn error(err) { <ErrorView {err} /> }}
    {fn ready(user) { <Profile {user} /> }}
</Boundary>

There are no magic dynamic or portal tags. Boundary is an ordinary exported std::html function used in component position so its body can bind named view blocks; lowercase boundary(...) remains available for programmatic composition. In development, ZoloClient.inspect() exposes the attached call/DOM graph and ZoloClient.verifyAttach() reports impossible targets as TE749.

See examples/features/36-web/16-verniz-dx.zolo for one complete program.

Search Zolo

9 results

en