Overview

A directive is a special value you put in an expression. It runs instead of being written as text. Import these from mates next to html — the same names lit-html uses, except cache, which is exported as litCache.

classMap belongs in class=. ifDefined and live belong on an attribute or property. until, litCache, guard, keyed, and unsafeHTML belong in a child expression. repeat is on the Lists page.

What you might have used in JSX

Need
Helper
Notes
className object classMap class=${classMap({ card: true, on: active })}
Omit an optional attr ifDefined Removes the attribute when the value is undefined. null still writes “null”.
Force an input’s value live Writes the property even when the previous value looks unchanged.
key on one panel keyed Remounts when the key changes. See Lists.
Keep / skip a subtree litCache / guard litCache keeps DOM across a conditional. guard skips work until its values change.
Promise placeholder until Shows the fallback until the promise settles.
dangerouslySetInnerHTML unsafeHTML Trusted markup only.
Leave the DOM alone noChange A sentinel exported from mates beside html.

classMap and ifDefined

classMap turns truthy keys into classes. ifDefined omits the attribute when the value is undefined.

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

const App = () => {
const active = atom(false);
const tip = atom('');

return () => html`
<div class="m-col m-gap">
<div class=${classMap({ card: true, 'btn-primary': active() })}>
<p class="m-0">classMap toggles classes from an object</p>
</div>
<button class="m-self-start" title=${ifDefined(tip() || undefined)}>
${tip() ? 'Hover for tooltip' : 'No title attribute'}
</button>
<div class="m-flex m-gap-sm">
<button @click=${() => active.set((v) => !v)}>Toggle active</button>
<input
.value=${tip()}
@input=${(e) => tip.set(e.target.value)}
placeholder="Tooltip text…"
/>
</div>
</div>
`;
};
renderApp(App, document.getElementById('app'));

live() writes the property every update

live(value) forces the DOM property to match, which matters when a reset must overwrite what the user typed.

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

const App = () => {
const value = atom('Ada');
return () => html`
<div class="m-col m-gap">
<input
.value=${live(value())}
@input=${(e) => value.set(e.target.value)}
/>
<button @click=${() => value.set('Ada')}>Reset</button>
<p class="m-0">${value()}</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));

until() while a promise is pending

until(promise, fallback) shows the fallback, then replaces it with the settled value.

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

const loadName = () =>
new Promise((resolve) => setTimeout(() => resolve('Ada Lovelace'), 800));

const App = () => {
const n = atom(0);
return () => html`
<div class="m-col m-gap">
<button @click=${() => n.set((v) => v + 1)}>Reload</button>
<p class="m-0">
${until(loadName(), html`<span class="muted">Loading…</span>`)}
<span class="label"> #${n()}</span>
</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));