// Template function — no outer function, no atoms.
// The entire function re-runs with the parent.
const LikeCount = ({ likes }) => html`
  <span>${likes}</span>
`;

// Caller reads its own atom and passes the value:
//   html`… ${LikeCount({ likes: likes() })} …`

Overview

A component is a setup function that returns a template function. A template function is just the template function — the inner layer with nothing around it. It takes a props object and returns an html result directly.

Because there is no outer function, a template function cannot own atoms or lifecycle hooks, and it has no scheduler slot of its own: whenever the parent re-renders, the call re-executes and rebuilds its fragment. State must come in as props.

Because there is no scheduler, you call it directly — Badge({ label }) — exactly like any other function. No x() wrapper. It composes anywhere: inside .map(), in helper modules, as an argument.

Template function vs component

Capability
Template function
Component (outer/inner)
Local reactive state none Use atom() only in components.
Lifecycle hooks none onMount, onCleanup require a component outer function.
Independent re-rendering runs with parent Re-executes on every parent render. Components re-render only when their own dependencies change.
Composition style Direct call Call as Badge({ label }). Components need x(Component, props).
Props destructuring safe No outer/inner split — destructuring in the function body is always up-to-date.

Stateless Badge and UserRow template functions

Badge and UserRow take plain objects and return html``. No lifecycle, no state — just a function. Call them directly in a map() or anywhere in a parent template.

import { html, atom, renderApp } from 'mates';
// A template function — just a plain function returning html``, no state
const Badge = ({ label, variant = 'default' }) => {
const colors = { default: '#00b3a8', success: '#22c55e', warning: '#f59e0b', danger: '#ef4444' };
return html`
<span class="tag tag-soft"
style="background:${colors[variant] ?? colors.default}"
>${label}</span>
`;
};
const UserRow = ({ name, role, status }) => html`
<div class="card card-body m-flex m-items-center m-justify-between m-gap-sm">
<div>
<strong>${name}</strong>
<span class="muted">${role}</span>
</div>
${Badge({ label: status, variant: status === 'active' ? 'success' : 'default' })}
</div>
`;
const App = () => {
const users = atom([
{ name: 'Ada', role: 'Engineer', status: 'active' },
{ name: 'Alan', role: 'Designer', status: 'inactive' },
{ name: 'Grace',role: 'Manager', status: 'active' },
]);
return () => html`
<div class="m-col m-gap">
${users().map(u => UserRow(u))}
</div>
`;
};
renderApp(App, document.getElementById('app'));

Parent owns state; template function presents

Status is a pure template function. The parent atom drives updates — Status re-runs whenever the parent template re-renders.

import { html, atom, renderApp } from 'mates';

const Status = ({ ok }) => html`
<span class="tag tag-soft" style="background:${ok ? '#22c55e' : '#64748b'}">
${ok ? 'ok' : 'idle'}
</span>
`;

const App = () => {
const ok = atom(true);
return () => html`
<div class="m-col m-gap">
${Status({ ok: ok() })}
<button class="btn-primary" @click=${() => ok.set(v => !v)}>Toggle</button>
</div>
`;
};
renderApp(App, document.getElementById('app'));