Template syntax

A .azox file is HTML with three additions: braces for expressions, on: for events, and optional <script> and <head> blocks. It is not JSX — attributes keep their HTML names, and there is no className.

File structure

anatomy
<head>
  <!-- optional: copied into the document head -->
  <title>My page</title>
</head>

<script>
  // optional: imports and state
  import { signal } from 'azox/reactivity';
  const count = signal(0);
</script>

<main>
  <!-- the markup, exactly one root element -->
</main>

A page needs a single root element in its markup, the way a document has one <body>. Wrap siblings in a container.

Interpolation

Braces evaluate an expression and insert the result as text.

interpolation
<p>Hello, {name()}</p>
<p>Total: {price() * quantity()}</p>
<p>{items().length} items</p>

Values are escaped, so text that looks like markup is shown, not interpreted.

Attributes

Quoted values are static; braces make an attribute reactive.

attributes
<div class="card">static</div>

<div class={theme()}>reactive</div>
<img src={user().avatar} alt="Avatar" />
<a href={'/user/' + id()}>Profile</a>

A reactive attribute gets its own effect, so it updates independently of everything else on the page.

Events

Prefix any DOM event with on:. The expression is used as the listener directly.

events
<button on:click={() => count.set(count() + 1)}>
  Add one
</button>

<input on:input={(e) => name.set(e.target.value)} />

<form on:submit={handleSubmit}>
  <button type="submit">Send</button>
</form>

Handlers can contain object literals and nested braces — on:click={() => user.set({ name: 'Ada' })} parses correctly.

Literal text

To show markup or braces as-is — a code sample, for instance — wrap it in <text>. Nothing inside is parsed as markup or interpolated.

literal text
<pre><text><button on:click={handler}>Not parsed</button>

This documentation is written in Azox and uses it for every snippet.

Comments

comments
<!-- an HTML comment, kept in the output -->

<script>
  // a JavaScript comment, stripped from the output
</script>

Reference

SyntaxMeaning
{expr}Insert the value of an expression, escaped
attr={expr}A reactive attribute
attr="value"A static attribute
on:event={fn}Attach a DOM event listener
<Capitalised />A component
<slot />Where a component places its children
<text>…</text>Literal content, never parsed

Two-way inputs

bind: keeps an element and a signal in step in both directions, so you do not write a value= and an on:input= that have to agree.

bind:value
<script>
  import { signal } from 'azox/reactivity';
  const draft = signal('');
</script>

<input bind:value={draft} />
<p>{draft().length} characters</p>

The element follows the signal, and the signal follows the element. Setting the signal from anywhere else updates the input too.

It picks the right property and event

Written by hand these are easy to get wrong, and wrong quietly.

three kinds of input
<!-- binds `checked`, listens for "change" -->
<input type="checkbox" bind:checked={agree} />

<!-- read as a number, so qty() * 2 is 10 and not "52" -->
<input type="number" bind:value={qty} />

<!-- a select listens for "change" -->
<select bind:value={size}>
  <option value="s">Small</option>
  <option value="l">Large</option>
</select>

Name the signal, do not call it: bind:value={draft}, not bind:value={draft()}. A binding has to write back, which a value cannot do — so the called form is rejected at build time rather than half working.

More than one root

A page or component may have several root elements. They are placed directly where the component is used, with nothing wrapped around them.

components/Row.azox
<script>
  const { label, value } = props();
</script>

<dt>{label}</dt>
<dd>{value}</dd>
used in a definition list
<dl>
  <Row label="Name" value="Ada" />
  <Row label="Role" value="Engineer" />
</dl>

This matters where the parent element restricts what may sit inside it: a <div> wrapper is invalid in a <ul>, a <dl> or a <tbody>.

Importing data

A <script> block can import a .json file, for a constant you would rather not write out by hand in several places.

reading a version
<script>
  import pkg from '../package.json' with { type: 'json' };
</script>

<span>v{pkg.version}</span>

The file is read once during the build. Server rendering evaluates against it, and the value is compiled into the page's module as a constant rather than imported — the file sits outside the build directory and is never deployed, so an import would fail in the browser.

Only the properties your markup reads are included. Importing package.json for a version does not ship the rest of the file to every visitor.

Importing a .js module is not supported. It would mean running your project's code during the build. Use a .json file for data, and azox/reactivity for signals — anything else is reported as an error rather than failing silently.

Next