Step 6 — Loops & Conditionals
Render lists and conditional content without JavaScript.
The loop tag
<loop data="#items#" as="item"> iterates over an array. Nested properties use dot notation: #item.name#.
<loop data="#products#" as="product">
<div>
<strong>#product.name#</strong>
<span>$#product.price#</span>
</div>
</loop>
Live loop example
Data is fetched via the <what> block and looped over in the template. Here's a hardcoded list of programming languages with their stats:
| Language | Type | Year |
|---|---|---|
| Rust | Systems | 2015 |
| Go | Systems | 2009 |
| TypeScript | Web | 2012 |
| Python | Scripting | 1991 |
| Elixir | Functional | 2012 |
<loop data="#languages#" as="lang">
<tr>
<td>#lang.name#</td>
<td>#lang.type#</td>
<td>#lang.year#</td>
</tr>
</loop>
Conditionals
Use <if ...> to show/hide content. <unless> is the negation. <else/> provides the fallback.
<if user.authenticated>
Welcome, #user.full_name#!
<else/>
Please log in.
</if>
<unless items>
<p>No items found.</p>
</unless>
<!-- Comparisons: quote string values, keep numbers bare -->
<if score gt 90>Grade: A</if>
<if status == "active">Active</if>
<!-- Combine conditions with and / or (and binds tighter) -->
<if user.authenticated and user.role == "admin">Admin tools</if>
<if role == "admin" or role == "editor">Staff area</if>
Always quote string values (status == "active") — a bare word on the right side is treated as another variable. There are no parentheses; nest <if> blocks for complex logic.
The cond attribute (legacy, still accepted)
The simplified form above is the recommended syntax. Earlier versions used a cond attribute with #...# wrapping — it still works, so older templates keep rendering:
<!-- Recommended -->
<if active_step == 2>Step 2!</if>
<if count gt 0>There are items.</if>
<unless error>No errors.</unless>
<!-- Legacy long-hand equivalent (still accepted): -->
<if cond="#active_step# == 2">Step 2!</if>
<if cond="#count# > 0">There are items.</if>
<unless cond="#error#">No errors.</unless>
In the simplified form, use keyword operators — a literal > would close the HTML tag:
| Keyword | Equivalent | Meaning |
|---|---|---|
gt |
> |
Greater than |
lt |
< |
Less than |
gte |
>= |
Greater than or equal |
lte |
<= |
Less than or equal |
Live conditional demo
Toggle a session flag and watch the UI change.
Conditionals are evaluated on the server when a page renders. This demo stays live by pairing the mutation
with a re-fetch: w-set updates the session, then w-get pulls a freshly rendered
partial into the card — no JavaScript written, no page reload.
<button
w-set="session.tut_mode = pro"
w-get="/w-partial/tut-mode"
w-target="#tut-mode-demo"
w-swap="innerHTML">Enable Pro</button>
Nested loops
Loops can be nested. Each loop has its own scope — use distinct as names. Here's a live example with categories and their items:
<loop data="#categories#" as="category">
<h3>#category.name#</h3>
<loop data="#category.items#" as="item">
<span>#item.name#</span>
</loop>
</loop>
Loop with empty state
<unless items>
<p class="text-gray-500">No items yet.</p>
</unless>
<loop data="#items#" as="item">
<div>#item.name#</div>
</loop>
<loop data="#items#" as="item">iterates over arrays- Nested data access:
#item.name#,#item.meta.created_at# <if var>,<else/>,<unless var>- Simplified:
<if count gt 0>— nocondor#...#needed - Keyword operators:
gt,lt,gte,lte - Nested loops: use distinct
asnames for each level - Loops and conditionals compose — conditionals work inside loops