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.

Standard mode. Click the button to switch.

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:

Frontend
React Vue Svelte
Backend
Rust Go Node
Database
SQLite Postgres
<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>
What you learned
  • <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> — no cond or #...# needed
  • Keyword operators: gt, lt, gte, lte
  • Nested loops: use distinct as names for each level
  • Loops and conditionals compose — conditionals work inside loops