π§© Reusable BlocksΒΆ
Cleraβs reusable block system lets you mark any HTML element as a reusable source and instantiate it anywhere with <use />. It is not a component system: it is reusable HTML with minimal syntax.
π― The modelΒΆ
Five pieces that work together:
Piece |
Role |
|---|---|
|
Definition-only source: not rendered |
|
Live source: renders in place and is reusable |
|
Void instantiation: no overrides |
|
Container instantiation: with |
|
Builds |
π§© Declaring a reusable sourceΒΆ
<template id="...">: definition onlyΒΆ
Native HTML <template> element. Not rendered. Used only as a reusable source. Preferred for blocks you never want appearing inline.
<template id="card">
<div class="card">
<h2>{name}</h2>
<p>{price}</p>
</div>
</template>
[template] attribute: live reusableΒΆ
Any element with the template attribute renders normally and is registered as a reusable source. Its clones become the binding targets, not the source itself.
<div template id="hero-card">
<div class="hero">
<h2>{title}</h2>
<p>{subtitle}</p>
</div>
</div>
π ID is requiredΒΆ
Every reusable source must have an id. Without it, Clera warns in dev mode:
[CLERA:TEMPLATE_ID_REQUIRED] A [template] element is missing an id attribute.
β οΈ No inner idsΒΆ
Elements inside templates must not use id attributes. Each clone would produce a duplicate id in the DOM. Clera warns in dev mode:
[CLERA:TEMPLATE_INNER_ID]
Use class for styling. Use slot= to name nodes for targeting (see below). Do not use id attributes inside templates.
β‘ Instantiating with <use />ΒΆ
<use template="id" /> is replaced by a clone of the referenced template when the page mounts. Clera uses querySelectorAll("use[template]") internally. SVG <use href="..."> elements are never affected.
π Two template classificationsΒΆ
Clera classifies every registered template as either void or container at registration time.
Classification |
Condition |
Required |
|---|---|---|
Void |
Source is a |
Self-closing: |
Container |
Source is a live element with child elements |
Open/close: |
The names come from HTML: void elements are self-closing (<br>, <img>). Same idea here. If your template has no children it is void and uses self-closing syntax. If it has children it is a container and uses open/close syntax.
Using the wrong form produces a shape mismatch warning and the <use> is removed:
[CLERA:USE_SHAPE_MISMATCH] <use template="card">: shape mismatch.
Void template requires self-closing <use />
π·οΈ Naming nodes with slot=ΒΆ
Container templates can give any descendant a name using the slot= attribute. Named nodes are the recommended way to identify targets for overrides.
<template id="product-row">
<div class="row">
<img slot="thumbnail" src="{img}" alt="">
<span slot="label">{name}</span>
<span slot="price">{price}</span>
</div>
</template>
slot= names survive into the expanded clone and are never stripped. This means you can also target them with CSS:
[slot="price"] { font-weight: bold; }
Slot names are your stable, readable handles for overrides. You write the name yourself, so it never changes unless you change it.
β οΈ
slot=is a Clera targeting attribute. It is not the same as theslotattribute used by native Web Components shadow DOM. Clera does not use shadow DOM.
π― Container overrides with target=ΒΆ
Container <use> elements can override specific nodes inside the clone. Each direct child of the <use> must carry a target= attribute identifying which node to replace.
target= accepts either a slot name or a raw data-cre-nid value. Slot names are resolved first.
<template id="product-row">
<div class="row">
<img slot="thumbnail" src="{img}" alt="">
<span slot="label">{name}</span>
<span slot="price">{price}</span>
</div>
</template>
<use template="product-row">
<span target="price">$49.00</span>
</use>
The target="price" child replaces the node with slot="price" in the clone.
About data-cre-nidΒΆ
When Clera registers a container template it stamps each descendant with a data-cre-nid attribute in depth-first order starting from 0. These are internal identifiers used by the renderer. You do not write them yourself.
target= accepts raw nid values as a fallback when no matching slot= name is found. Raw nids are supported for backward compatibility, but slot names are the recommended way to reference template nodes because they are readable and stable across structural changes.
π‘ Clera Studio overlays both slot names and nid values on the rendered template so you can see which nodes are targetable without inspecting the DOM manually.
Symmetric overrideΒΆ
The override element has the same tag as the template node it targets. Clera replaces only the text content. All attributes and the data-cre-nid value on the template node are preserved.
Use this when the structure stays the same and only the text changes.
<template id="price-row">
<div class="row">
<span slot="label">{label}</span>
<span slot="value">{value}</span>
</div>
</template>
<use template="price-row">
<span target="value">$49.00</span>
</use>
Result: the <span slot="value"> in the clone keeps its tag and attributes, but its text becomes $49.00.
Asymmetric overrideΒΆ
The override element has a different tag than the template node it targets. Clera removes the template node entirely and inserts the override element in its place. The node is also removed from Cleraβs internal renderer cache so it is not reused in future clones.
Use this when you need to swap the element type, for example replacing a <span> with an <a> or a <p> with an <h3>.
<template id="card">
<div class="card">
<span slot="title">{title}</span>
<p slot="body">{body}</p>
</div>
</template>
<use template="card">
<h2 target="title">Featured Product</h2>
</use>
Result: <span slot="title"> is removed and replaced with <h2>Featured Product</h2>. The <p slot="body"> is untouched and resolves from page data.
If a direct child of <use> is missing target=, Clera warns and discards the entire <use>:
[CLERA:USE_TARGET_REQUIRED] Direct child of <use template="product-row"> is missing a target= attribute.
If the target= value does not match any slot name or nid in the clone, Clera warns and discards the entire <use>:
[CLERA:UNKNOWN_TARGET] <use template="product-row">: target="badge" not found in clone.
π Two instantiation modesΒΆ
Named instance mode: with nameΒΆ
Each named <use> gets its own isolated data scope, addressable directly on context.
<use template="card" name="featured" />
<use template="card" name="sale" />
function loadStore(context) {
context.featured.name = "Notebook Pro";
context.featured.price = 1200;
context.sale.name = "Clera Phone";
context.sale.price = 699;
}
Duplicate name values on the same page produce a warning in dev mode. The second instance reuses the existing scope rather than creating a new one:
[CLERA:USE_NAME_DUPLICATE] Duplicate <use> instance name "featured" on page "store". Reusing existing scope.
π Data resolution orderΒΆ
Named instance modeΒΆ
Instance-local data (
context.instanceName.*)Page-local data
Global data
""fallback
πΊοΈ app.map(dataObject, string)ΒΆ
app.map() takes a data object and a string containing {key} placeholders, and returns a new string with those placeholders replaced by the matching values from the object.
app.map({ id: "a", name: "Notebook" }, `<use template="card" name="{id}" />`)
// returns: '<use template="card" name="a" />'
That is all it does. It is a find-and-replace helper that works on strings. It does not loop over a list, it does not touch the page, and it does not render anything. You call it once per item and collect the results yourself.
const products = [
{ id: "notebook-pro", name: "Notebook Pro" },
{ id: "clera-phone", name: "Clera Phone" }
];
let html = "";
for (const product of products) {
html += app.map(product, `<use template="card" name="{id}" />`);
}
// html is now:
// '<use template="card" name="notebook-pro" /><use template="card" name="clera-phone" />'
context.render("#products", html);
Each call to app.map() produces one <use> string. Joining them all and passing the result to context.render() is what puts the list on screen.
Three placeholder systemsΒΆ
When building dynamic lists you will encounter three different placeholder syntaxes. They look similar but they are handled at completely different stages, by different systems.
Syntax |
What handles it |
When |
|---|---|---|
|
JavaScript |
Before anything else. Evaluated when your script runs. |
|
|
When you call |
|
Clera |
Later, when Clera patches the DOM with reactive data. |
The rule is: each placeholder is handled exactly once, by exactly one system, in the order above.
This means you can use all three in the same string without them interfering:
const label = "Price";
app.map(
{ id: "notebook-pro", currency: "USD" },
`<use template="price-row" name="{id}" data-label="${label}" data-currency="{currency}" />`
)
Here ${label} is replaced by JavaScript when the line runs. {id} and {currency} are replaced by app.map(). Any {path} inside the template itself is replaced by Clera at render time. None of them overlap.
π‘ Dynamic list renderingΒΆ
<template id="product-card">
<div class="product">
<h3 slot="name">{name}</h3>
<p slot="price">{price}</p>
</div>
</template>
<div id="products"></div>
async function loadProducts(context) {
const products = await context.fetch("/api/products");
let html = "";
for (const product of products) {
html += app.map(product, `<use template="product-card" name="{id}" />`);
}
context.render("#products", html);
for (const product of products) {
context[product.id].name = product.name;
context[product.id].price = `$${product.price}`;
}
}
π‘ Static named instancesΒΆ
For a small fixed number of known instances, declare them directly without a loop:
<template id="stat-card">
<div class="stat">
<span slot="label" class="label">{label}</span>
<span slot="value" class="value">{value}</span>
</div>
</template>
<use template="stat-card" name="users" />
<use template="stat-card" name="sales" />
<use template="stat-card" name="revenue" />
function loadDashboard(context) {
context.users.label = "Total Users";
context.users.value = "12,481";
context.sales.label = "Sales Today";
context.sales.value = "342";
context.revenue.label = "Revenue";
context.revenue.value = "$48,200";
}
β οΈ Reserved instance namesΒΆ
Instance names that match Cleraβs built-in context properties (navigate, render, data, etc.) are rejected. Choose a different name.