Browse documentation

Template authoring / Reference

What a template can render

Look up how a template position interprets values, arrays, and producers while retaining their errors and requirements.

Template parts accept ordinary values, arrays, nested templates, and Effect, Fx, or Stream producers. The position determines how those values are used: a child becomes output, while an attribute or property updates that field. Use this reference after keyed collections when an input’s rendering behavior is unclear.

Let the position decide the interpretation

A value accepted by the broad Renderable contract is not meaningful in every position. An object can describe .data keys or a DOM property; that does not turn an arbitrary record into child markup. Named element parts select which field to update.

InputIn a child positionIn a named element part
string, number, biginttextserialized attribute or direct property value
booleantext, including "false"attribute string or boolean-part truthiness
null or undefinedempty outputattribute removal or direct property assignment
arrayordered normalized entriesclass collection or the property’s array value
OptionNone is empty; Some selects its contained valueinterpreted by the chosen part
Effectobtain its result, then normalize itobtain the field’s value
Fx, Stream, RefSubjectupdate the local output as values arriveupdate the captured field
nested template or RenderEventrenderer output in this positionnot a generic attribute value

A boolean child is displayed data. Return null or the desired view when a condition should hide content. Arrays are recognized; arbitrary Sets, generators, and iterables are not implicitly rendered collections. Convert a fixed iterable to an array deliberately.

Distinguish a value from the producer of that value

A string already exists. An Effect describes work that can obtain a value once. An Fx or Stream describes values arriving over time. The following view puts each into a different part:

import { Effect, Stream } from "effect";
import { Fx } from "@typed/fx";
import { html } from "@typed/template";

const name = Effect.succeed("Ada");
const count = Fx.fromIterable([1, 2, 3]);
const status = Stream.fromIterable(["Loading", "Ready"]);

export const view = html`<section>
  <h1>Hello, ${name}</h1>
  <p>Count: ${count}</p>
  <output>${status}</output>
</section>`;

In a DOM render, each count emission updates its own position; it does not append another paragraph. The Effect runs when rendering starts, not when the module defines name. A RefSubject can replace the short demonstration Fx without changing the template’s interpolation syntax.

Return the output that fits the component

A helper can return several pieces without adding a wrapper element. No component generator is needed when there is no setup:

import { html } from "@typed/template";

export const EmptyResults = (query: string) => [
  html`<h2>No results for ${query}</h2>`,
  html`<p>Try another search.</p>`,
];

Interpolate EmptyResults(query) as an ordered group. No wrapper is added around the heading and paragraph. Components with setup can return these forms too; see component construction. Returning a DOM node makes the output browser-specific.

An array describes order, not enduring record identity. For records that can be inserted, removed, or reordered, use keyed collections.

Keep errors and service requirements attached to the view

Interpolating an Effect preserves its error and requirement types in the resulting view:

import { Effect } from "effect";
import { html } from "@typed/template";

export const Heading = <E, R>(title: Effect.Effect<string, E, R>) =>
  html`<h1>${title}</h1>`;

Heading retains the title producer’s E and R; its caller must handle failures and supply required services before running the view. See the Renderable reference for channel inference and services and lifetime for providing dependencies.

Flatten producer relationships before rendering

An ordinary Fx’s emitted values are update payloads. Emitting another Fx does not make the template recursively subscribe to a new nested producer. If a query selects a request stream, choose the intended switching or concurrency operator first and give the part the resulting output stream. An Effect’s result is normalized; an Fx’s emissions are not recursively flattened into subscriptions.

Server rendering takes initial ordinary producer values, so a source with no initial value can stall server output. See rendering HTML on the server for that boundary and the distinct protocol for ordered renderer-owned HTML chunks.