Skip to content

Writing templates

A template body is JSX-flavored markup. This page covers the body itself; directives (on:, bind:, class:list) have their own page.

Plain {expr} renders once and is auto-escaped. read(machine, selector) creates a live binding:

<h3>{product.name}</h3> <!-- static -->
<p>Total: {read(cart, c => c.total)}</p> <!-- updates when the cart changes -->

read() is only valid in text or as a whole attribute value. Mixing literal text with a read() in one attribute is a compile error — wrap the whole value in the selector instead.

Attribute bindings understand boolean semantics: a selector returning false/null/undefined renders the attribute absent (and a change patches it away with removeAttribute); true renders it present-and-empty. That’s how presence-toggled attributes work end to end:

<button disabled={read(cart, (c) => c.count === 0)}>Begin checkout</button>

One platform caveat: checked, value, and selected as attributes set defaults only — a form control the user has touched ignores them. Live form state is bind: territory.

Both are callbacks, not components, so a branch’s body isn’t evaluated unless it’s chosen.

{when(read(cart, c => c.isEmpty), () =>
<p>Your cart is empty.</p>
)}
{match(read(order, o => o.status), {
pending: () => <span>Pending</span>,
shipped: () => <span>Shipped</span>,
})}

Use when for one condition, match to pick one of several by value.

<ul>
{each(read(cart, c => c.items), (item, i) =>
<li>{item.quantity} × ${item.unitPrice.toFixed(2)}</li>
)}
</ul>

raw() emits a string verbatim, bypassing escaping. Pass only markup you constructed or trust:

import { raw } from '@statorjs/stator/template'
<div>{raw(sanitizedHtml)}</div>

For a schema.org block, use the typed component rather than a hand-written <script>:

import { JsonLd } from '@statorjs/stator/components'
<JsonLd json={{ "@type": "Product", name: "Pocket Notebook" }} />

A capitalized tag invokes a component; lowercase is HTML. Pass machines and data as props:

<ProductList products={products} cart={cart} />

Layouts and named slots (<children>) are covered in Routing.