Standard JSX syntax works. This page covers BarefootJS-specific behavior and limitations.
#Control Flow
// Ternary
{count() > 0 ? <p>{count()} items</p> : <p>No items</p>}
// Logical AND
{isLoggedIn() && <Dashboard />}
// Conditional return
if (status === 'empty') {
return <p>No items yet.</p>
}
return <div>...</div>#List Rendering
{todos().map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}.filter().map() chains work when the predicate uses simple expressions or block bodies with if/return:
// ✅ Simple predicate
{todos().filter(t => !t.done).map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
// ✅ Block body with simple statements — also works
{todos().filter(t => {
const f = filter()
if (f === 'active') return !t.done
if (f === 'completed') return t.done
return true
}).map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}.sort() and .toSorted() can be chained with .map() and .filter():
// ✅ Sort then render
{items().sort((a, b) => a.price - b.price).map(item => (
<Item key={item.id} item={item} />
))}
// ✅ Filter, sort, then render
{items().filter(x => x.active).sort((a, b) => a.name.localeCompare(b.name)).map(item => (
<Item key={item.id} item={item} />
))}
// ✅ Multi-key: sort by price, break ties by name
{items().sort((a, b) => a.price - b.price || a.name.localeCompare(b.name)).map(item => (
<Item key={item.id} item={item} />
))}
// ✅ Relational ternary
{items().toSorted((a, b) => a.price > b.price ? 1 : -1).map(item => (
<Item key={item.id} item={item} />
))}Supported comparator shapes: (a, b) => a - b, (a, b) => a.field - b.field, (a, b) => a.localeCompare(b), (a, b) => a.field.localeCompare(b.field), relational-ternary returns ((a, b) => a.field > b.field ? 1 : -1, including the 3-way a < b ? -1 : a > b ? 1 : 0 form), and any of these ||-chained for multi-key tie-breaks. A single-return block body ((a, b) => { return a.field - b.field }) works too. Reverse the operands (or the ternary sign) for descending order. Other shapes — function references (sort(myCmp)), multi-statement block bodies, and localeCompare(b, locale, opts) — produce a compile error; use /* @client */ in that case.
#Event Handling
<button onClick={() => setCount(n => n + 1)}>+1</button>
<input onInput={(e) => setText((e.target as HTMLInputElement).value)} />
<input onKeyDown={(e) => e.key === 'Enter' && handleSubmit()} />#Dynamic Attributes
<button disabled={!accepted()}>Submit</button>
<a className={filter() === 'all' ? 'selected' : ''}>All</a>
<div style={`background: ${accepted() ? '#4caf50' : '#ccc'}`}>...</div>#Limitations
Some JavaScript expressions cannot be translated into marked template syntax. Which expressions error depends on the adapter — non-JS template backends (Go html/template, Mojolicious EP) have a narrower expression surface than JS-runtime adapters (Hono, etc.) that can execute JS at SSR time.
| Pattern | Hono / JS-runtime adapters | Go / Mojo adapters |
|---|---|---|
.filter() with destructured param (({done}) => done) |
works (runs as JS) | BF101 |
.filter() with function keyword callback |
works | BF101 |
.reduce(), .forEach(), .flatMap() |
works | BF101 |
Nested higher-order in filter predicate (x => x.tags.filter(...).length > 0) |
works | BF101 |
Sort comparator that's a function reference, multi-statement block body, or localeCompare(b, locale, opts) |
BF021 (all adapters) | BF021 |
typeof in a filter predicate |
BF021 (all adapters) | BF021 |
BF021 is raised at the IR layer and applies to every adapter. BF101 is raised by adapters that can't lower the expression to their template language. Either way, add /* @client */ to opt into client-only evaluation and suppress the error.
#Patterns that error on Go / Mojo
Nested higher-order methods:
// ❌ BF101 on Go/Mojo; works on Hono
{items().filter(x => x.tags.filter(t => t.active).length > 0).map(...)}
// ✅ Add /* @client */ to evaluate on the client
{/* @client */ items().filter(x => x.tags.filter(t => t.active).length > 0).map(...)}.reduce() / .forEach() / .flatMap():
// ❌ BF101 on Go/Mojo; works on Hono
{items().reduce((sum, x) => sum + x.price, 0)}
// ✅ Use /* @client */
{/* @client */ items().reduce((sum, x) => sum + x.price, 0)}Destructuring in predicate parameters:
// ❌ BF101 on Go/Mojo; works on Hono
{items().filter(({done}) => done).map(...)}
// ✅ Use a named parameter for adapter portability
{items().filter(t => t.done).map(...)}Function expressions (function keyword):
// ❌ BF101 on Go/Mojo; works on Hono
{items().filter(function(x) { return x.done })}
// ✅ Use arrow functions for adapter portability
{items().filter(x => x.done)}#Patterns that error on all adapters
Unsupported sort comparators (multi-statement block bodies, function references):
// ❌ BF021 (all adapters)
{items().sort((a, b) => { const an = a.name; return an > b.name ? 1 : -1 }).map(item => (
<Item key={item.id} item={item} />
))}
// ✅ Use /* @client */
{/* @client */ items().sort((a, b) => { const an = a.name; return an > b.name ? 1 : -1 }).map(item => (
<Item key={item.id} item={item} />
))}See the TodoApp example for a real-world component using /* @client */.