Templates
JAF's template syntax is called JEX - JavaScript Expressions in HTML.
Write ordinary HTML, and put JavaScript inside curly braces:
<script>
let name = "Ada"
let count = 3
</script>
<h2>Hello, {name}!</h2>
<p>You have {count * 2} messages.</p>
That is the whole of it. Anything inside { } is a plain
JavaScript expression, and it updates automatically when a variable it reads
changes.
Basic Expressions
Use curly braces {expression} for any JavaScript expression:
{name} <!-- variable -->
{count * 2} <!-- math -->
{user.name} <!-- property access -->
{formatDate(date)} <!-- function call -->
{count > 5 ? 'High' : 'Low'} <!-- ternary -->
{items.length} <!-- method/property -->
Attribute Expressions
JEX works in attributes too - and they're reactive:
<!-- Pure expressions - quotes optional -->
<a href={url}>Link</a>
<img src={avatarUrl} alt={userName} />
<div class={isActive ? 'active' : 'inactive'}>
<!-- Mixed static + expression - quotes required -->
<a href="/users/{userId}">Profile</a>
<div class="card {isActive ? 'active' : ''}">
Attribute expressions update automatically when referenced variables change.
Conditionals
Use ternary or logical operators. No special {#if} blocks needed:
<!-- Ternary for either/or -->
{loggedIn ? <span>Welcome!</span> : <a href="/login">Log in</a>}
<!-- && for show/hide -->
{error && <p class="error">Something went wrong</p>}
{user && <UserCard name={user.name} />}
<!-- In attributes -->
<div class="card {featured ? 'featured' : ''}">
Lists
Use .map() - it's just JavaScript:
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
<!-- With index -->
{items.map((item, i) => <li>{i + 1}: {item}</li>)}
<!-- With objects -->
{users.map(user => <div>{user.name}</div>)}
<!-- Chained methods -->
{users.filter(u => u.active).map(user => <li>{user.name}</li>)}
Keyed Lists
A key tells JAF which row is which, so a row keeps the DOM node it
already had. Add one whenever the list can change shape:
{users.map(user => <li key={user.id}>{user.name}</li>)}
Without a key, three ordinary things rebuild every row from scratch:
- Reordering - sorting or dragging rows around rewrites them in place instead of moving them.
- Inserting in the middle - every row after the new one is rewritten.
- Re-fetching - fresh objects for the same data look like a completely new list.
A rebuilt row loses whatever the browser was holding in it: focus, text the user had typed into an input, a playing video, scroll position inside the row.
With a key, those rows are moved rather than rebuilt and all of that survives.
The <for> element keys automatically by object identity, so
a simple read-only <for> list needs no key.
In .map(), always write the key yourself.
Very Large Lists
Rows arrive at the template as reactive objects, which is what makes
rows[5].label = 'x' update one cell.
With thousands of rows, that per-row bookkeeping becomes the most expensive
thing on the page. {@static rows.map(...)} turns it off: the
rows are handed to the template raw.
Assigning a new list still re-renders as normal. What stops working is
in-place mutation - rows[5].label = 'x' no longer updates the
cell. See For for the full trade-off.
Protected Regions
Some elements don't process curly braces - they're treated as literal text:
- <pre> - Preformatted text
- <code> - Code blocks
- <style> - CSS rules
This means you can write CSS with curly braces as normal:
<style>
.card { padding: 1rem; } <!-- Not treated as JEX -->
</style>
JEX is Not JSX
It's like React JSX expressions, but in actual HTML files.
| JEX | JSX | |
|---|---|---|
| Lives in | a .html file, next to its script and style |
a .jsx or .tsx file |
| Attributes | standard HTML names: class, for |
DOM property names: className, htmlFor |
| Events |
onclick="save()" or onclick={() => save(id)}
|
onClick={save} |
| Inline style |
a CSS string: style="color: red", or the
style: directive
|
an object: style={{ color: 'red' }} |
| Several top-level elements | no wrapper needed | need a fragment: <>...</> |
Common mistakes when coming from React:
<!-- WRONG: JSX patterns -->
<div className="card"> <!-- Use class, not className -->
<button onClick={handler}> <!-- Use onclick, not onClick -->
<!-- RIGHT: Standard HTML -->
<div class="card">
<button onclick="handler()">