Home/Docs/Quick Reference
Advanced

Quick Reference

A condensed cheat-sheet covering all template syntax at a glance.

cheatsheet.html
<!-- TEXT (auto-escaped) -->
{state.value}
{a > b ? 'x' : 'y'}

<!-- RAW / UNESCAPED HTML (opt out of escaping) -->
<div html="state.articleHtml"></div>
{olum.html(state.snippetHtml)}

<!-- STRING ATTRIBUTES (literal + {expr}) -->
<div class="box {state.cls}" style="color:{state.color}" title="Hi {state.name}"></div>

<!-- BOOLEAN ATTRIBUTES (whole-value {expr} → presence toggle) -->
<button disabled="{state.busy}">Save</button>
<details open="{state.expanded}"></details>

<!-- ASSET URLS (root-absolute, served from public/) -->
<img src="/assets/logo.svg" />

<!-- EVENTS (code in "") — PLAIN ELEMENTS ONLY; on a component tag on* is a prop, see FUNCTION PROPS -->
<button onclick="save()">Save</button>
<input oninput="(e)=> state.text = e.target.value" />
<input oninput="setValue($event)" />
<form onsubmit="(e)=> { e.preventDefault(); submit() }"></form>
<input oninput="draft($event)" onblur="save()" />   <!-- several on* attributes on one element are fine -->

<!-- FORM BINDINGS (manual two-way) -->
<input value="{state.text}" oninput="(e)=> state.text = e.target.value" />
<input type="number" value="{state.n}" oninput="(e)=> state.n = +e.target.value" />
<input type="checkbox" checked="{state.on}" onchange="(e)=> state.on = e.target.checked" />
<input type="radio" checked="{state.pick === 'a'}" onchange="state.pick = 'a'" />
<select onchange="(e)=> state.color = e.target.value">
  <option value="red" selected="{state.color === 'red'}">red</option>  <!-- selected on option, NOT value on select -->
</select>
<select multiple onchange="(e)=> state.picked = Array.from(e.target.options).filter(o=>o.selected).map(o=>o.value)"></select>
<input type="file" multiple onchange="(e)=> state.files = e.target.files" />   <!-- FileList → Array.from() to loop -->
<input type="date" value="{state.date}" oninput="(e)=> state.date = e.target.value" />  <!-- value is a STRING -->
<textarea value="{state.text}" oninput="(e)=> state.text = e.target.value"></textarea>
<div contenteditable="true" oninput="(e)=> state.title = e.target.textContent">{state.title}</div>

<!-- FORM: validation + submit + FormData (see /docs/forms) -->
<form onsubmit="(e)=> { e.preventDefault(); state.data = Object.fromEntries(new FormData(e.target)) }" onreset="reset()">
  <input name="user" value="{state.user}" oninput="set($event)" oninvalid="(e)=> { e.preventDefault(); state.err = e.target.validationMessage }" required minlength="3" />
  <fieldset disabled="{!state.ship}"></fieldset>   <!-- disables every control inside -->
  <button type="submit" value="publish">Publish</button>   <!-- e.submitter tells you which -->
</form>

<!-- CONDITIONALS -->
<if when="state.tab === 'a'"></if>
<else-if when="state.tab === 'b'"></else-if>
<else></else>

<!-- SHOW -->
<show when="state.visible"></show>

<!-- DEBUG -->
<log>{state.user}</log>          <!-- console.log(state.user) on mount + each time user changes; renders nothing -->

<!-- TRANSITIONS (enter/leave animation; works on <if>/<show> toggles AND keyed <for> items) -->
<if when="state.visible">
  <transition transition="fade">              <!-- same in+out; built-ins: fade/fly/scale/slide/draw -->
    <p>fades in and out</p>
  </transition>
</if>
<transition in="fly({ y: 200 })" out="fade"><p></p></transition>  <!-- separate in/out + params -->
<transition flip="{ duration: 300 }"><li></li></transition>       <!-- animate to new spot on keyed reorder -->
<!-- crossfade: const _cf = crossfade({ fallback: transitions.scale }); const send=(n,p)=>_cf[0](n,p); receive=… -->
<transition in="receive({ key: id })" out="send({ key: id })" flip><li></li></transition>  <!-- fly between two keyed lists -->

<!-- LOOPS -->
<for each="item of state.items" key="item.id"><Row item="{item}" /></for>
<for each="i of 6">{i}</for>
<for each="key in state.map">{key}</for>
<for each="(item, index, arr) of state.items">{index}: {item}</for>
<for each="(key, index, value) in state.map">{key} = {value}</for>

<!-- COMPONENTS + PROPS + SLOT -->
<Card title="Hi" count="{n + 1}" data="{state.obj}">
  <span>slot content → {children}</span>
</Card>

<!-- PROPS (in the child's <script>) -->
const { title, children } = props();  // destructured names stay LIVE (compiled to fresh reads)
const { size = "md" } = props();      // default value when the parent omits it
{title} {children}                    // use the names anywhere — always the LATEST values
{props().title}                       // equivalent direct read

<!-- FUNCTION PROPS (component events: PascalCase tag = your component, lowercase = real HTML) -->
<!-- a NAME only, never inline code -->
<Inner onMessage="{handleMessage}" />  <!-- child: const { onMessage } = props(); onMessage(payload) -->
<Inner onMessage="(e)=> …" />          <!-- ✗ arrives as a plain string -->
<Inner onMessage="{(e)=> …}" />        <!-- ✗ inline fn / obj.fn / fn(arg) → arrives undefined -->
<Row row="{row}" onPick="{pick}" />    <!-- pre-bind data by passing it, child calls onPick(row.id) -->

<!-- DERIVED VALUES (plain functions) -->
const doubled = () => state.count * 2;
{doubled()}

<!-- host: this component's root element, inside onMount -->
onMount(() => { host.querySelector("main"); });
💡

The design principle

OlumJS draws one line: is the attribute fundamentally code or a string? Native HTML already answers it (onclick="" is code, class="" is a string). Code-shaped attributes (when / each / key / on* / html) take an expression directly in ""; everything else is a literal string with {} for the dynamic bits. No naked braces, no colon-bindings, no special directives to memorize.