Skip to content

Templates

Templates are tagged template literals. There is no JSX, no compiler and no runtime parser of your own — the browser’s HTML parser does the work, once.

import { html, render } from '@alacris/core';
render(html`<p>hello</p>`, document.body);

Each unique template literal is parsed once per call site into a native <template> element plus a list of the positions that are dynamic. Rendering it is cloneNode, and updating is a direct write to the node that changed.

A function in a ${} is a live binding. A value is written once.

html`<p>${count}</p>` // live — count is a signal, so a function
html`<p>${count()}</p>` // a snapshot: you read it, so it is a string
html`<p>${() => a() + b()}</p>` // live, derived, updates when either changes

That is the whole model. Everything below is where a ${} can go.

html`<div>${value}</div>`

Accepts text, numbers, DOM nodes, other templates, arrays and each. null, undefined, booleans and '' render nothing, which makes conditionals read naturally:

html`<div>${() => error() && html`<p class="error">${error}</p>`}</div>`

Swapping between two templates reuses the DOM when the template is the same and replaces it when it is not:

html`<div>${() => (loggedIn() ? html`<x-account />` : html`<x-signin />`)}</div>`
html`<a href=${url} title=${tip}>link</a>`

An attribute is removed entirely when its value is null, undefined or false. Values can also be spliced into static text:

html`<div class="card card--${variant}">`

Prefix with . to set a DOM property instead of an attribute. Casing is preserved, which attributes cannot do:

html`<input .value=${text} />`
html`<div .innerHTML=${trusted}></div>`
html`<x-child .rows=${rows}></x-child>`

On a custom element, an unprefixed binding already sets the property, so rows=${rows} and .rows=${rows} are the same — objects, arrays and camelCase names round-trip. Use the dot on native elements (.value, .checked) and when the name should stay an attribute (data-*, aria-*).

Prefix with ?. The attribute is present when truthy and absent otherwise:

html`<button ?disabled=${() => !valid()}>Save</button>`

Prefix with @:

html`<button @click=${(e) => count(count() + 1)}>+</button>`

Handlers are values, never reactive expressions — the function you pass is the handler. Modifiers compose:

html`<button @click.once=${init}>`
html`<form @submit.prevent=${save}>`
html`<div @click.stop=${select}>`
html`<div @scroll.passive=${onScroll}>`

.stop, .prevent, .once, .capture and .passive are supported.

Both accept objects and arrays, so you rarely build these strings by hand:

html`<div class=${{ card: true, 'card--wide': wide(), [size()]: true }}>`
html`<div class=${['card', isActive() && 'is-active']}>`
html`<div style=${{ width: pct() + '%', '--tone': color() }}>`

Custom properties work in the object form, which is the cheapest way to drive CSS from a signal — one property write, no stylesheet re-parse. A key you stop passing is cleared.

Get the element itself:

let input;
html`<input ref=${(el) => (input = el)} />`

There is no ${...props} in a tag. Set properties explicitly, or use a ref and assign them.

Use the svg tag for fragments that belong inside an <svg>, so children get the right namespace:

import { svg } from '@alacris/core';
svg`<circle cx="10" cy="10" r=${radius} />`

For anything outside a component. Returns a disposer that removes the DOM and stops every binding:

import { html, render } from '@alacris/core';
const dispose = render(html`<p>${count}</p>`, document.querySelector('#app'));
dispose();

Bindings go in attribute or child positions. These are not supported, and the compiler throws rather than guessing:

html`<${tag}>` // dynamic tag names
html`<div ${attrName}="x">` // dynamic attribute names
html`<textarea>${value}</textarea>` // use .value instead

An attribute with a binding must be quoted or whole — class="a ${b}" or class=${b}, not class=a${b}.