what

Conditionals & Loops

Show content based on conditions. Iterate over collections. Handle pagination.

If / Else

Use <if> to conditionally render content. The cond attribute accepts a variable or comparison expression.

<if user.authenticated>
  <p>Welcome back, #user.full_name#!</p>
</if>

Add an <else/> block for the alternative:

<if user.authenticated>
  <p>Welcome back, #user.full_name#!</p>
</if>
<else/>
  <p>Please <a href="/login">log in</a> to continue.</p>
</else>

Chain multiple conditions with <elseif/>:

<if user.role == admin>
  <span class="badge">Admin</span>
</if>
<elseif user.role == editor/>
  <span class="badge">Editor</span>
</elseif>
<else/>
  <span class="badge">Member</span>
</else>

Comparison Operators

The cond attribute supports these operators:

OperatorMeaningExample
==Equals#status# == active
!=Not equals#role# != guest
>Greater than#count# > 0
<Less than#score# < 50
>=Greater than or equal#age# >= 18
<=Less than or equal#stock# <= 5

Simplified Syntax

You can omit the cond attribute and the #...# wrapping. Write conditions directly as the tag body:

<if active_step == 2>
  <p>You're on step 2!</p>
</if>

<if count gt 0>
  <p>There are items.</p>
</if>

<unless error>
  <p>No errors found.</p>
</unless>

<if user.role == admin>
  <span>Admin</span>
</if>
<elseif user.role == editor>
  <span>Editor</span>
</elseif>
<else/>
  <span>Member</span>
</else>
Both syntaxes work. <if cond="#count# > 0"> and <if count gt 0> are equivalent. The simplified form is recommended.

Keyword Operators

Inside HTML tags, > and < would close the tag prematurely. Use keyword operators instead:

KeywordEquivalentMeaning
gt>Greater than
lt<Less than
gte>=Greater than or equal
lte<=Less than or equal
<if age gte 18>
  <p>You can vote.</p>
</if>

<if stock lt 5>
  <span class="badge">Low stock</span>
</if>

Without an operator, the condition is truthy — it passes if the variable exists and is not empty, false, 0, or null.

<!-- Truthy check: passes if flash.success has a value -->
<if flash.success>
  <div class="alert">#flash.success#</div>
</if>

Prefix a truthy check with ! to negate it — it passes when the variable is empty, missing, or false:

<!-- Passes when no room is selected yet -->
<if !query.room>
  <a href="/start">Pick a room</a>
</if>

Unless

The <unless> tag is an inverted <if>. It renders its content when the condition is false.

<unless errors>
  <p>All fields are valid.</p>
</unless>

<unless user.authenticated>
  <a href="/login">Log in</a>
</unless>
Tip: Use <unless> when you want to avoid a negated condition. It reads better than <if error != true>.

Loops

Use <loop> to iterate over arrays. The data attribute points to the collection, and as names the loop variable.

<what>
fetch.posts = "local:posts"
</what>

<loop data="#posts#" as="post">
  <article>
    <h2>#post.title#</h2>
    <p>#post.excerpt#</p>
  </article>
</loop>

Loops also work with objects, exposing #key# and #value# for each entry:

<loop data="#settings#" as="setting">
  <dt>#key#</dt>
  <dd>#setting#</dd>
</loop>

Nested Loops

Loops can be nested. Each loop has its own scope, so use distinct as names:

<loop data="#categories#" as="category">
  <h2>#category.name#</h2>
  <loop data="#category.products#" as="product">
    <p>#product.name# — #product.price|currency#</p>
  </loop>
</loop>

Loop Variables

Inside a loop, these variables are automatically available:

VariableTypeDescription
#index#NumberZero-based index (0, 1, 2, ...)
#index1#NumberOne-based index (1, 2, 3, ...)
#first#Booleantrue for the first item
#last#Booleantrue for the last item
#loop_total#NumberTotal number of items in the collection
#loop_pages#NumberTotal number of pages (with pagination)
#loop_page#NumberCurrent page number (with pagination)
<loop data="#users#" as="user">
  <div class="user-row">
    <span>#index1#. #user.name#</span>
    <if first>
      <span class="badge">First!</span>
    </if>
    <if last>
      <span class="badge">Last!</span>
    </if>
  </div>
</loop>

Pagination

Add paginate and page attributes to a loop to split results across pages:

<loop data="#posts#" as="post" paginate="10" page="#query.page|default:1#">
  <article>
    <h2>#post.title#</h2>
    <p>#post.excerpt#</p>
  </article>
</loop>

<what-pagination/>

The paginate attribute sets items per page. The page attribute reads the current page from the query string (with a default of 1).

The <what-pagination/> component renders navigation links automatically — previous, next, page numbers, and ellipsis for large ranges.

Tip: <what-pagination/> is a built-in component. It reads the pagination state from the most recent paginated loop and generates accessible navigation with proper aria attributes.

Empty State

Handle empty collections by combining loops with conditionals:

<what>
fetch.tasks = "local:tasks"
</what>

<if tasks>
  <loop data="#tasks#" as="task">
    <div>#task.title#</div>
  </loop>
</if>
<else/>
  <p class="text-muted">No tasks yet. Create your first one!</p>
</else>

You can also use <unless> for a more direct approach:

<unless tasks>
  <div class="empty-state">
    <p>Nothing here yet.</p>
    <a href="/tasks/new">Add your first task</a>
  </div>
</unless>

<loop data="#tasks#" as="task">
  <div>#task.title#</div>
</loop>
Note: A loop over an empty or undefined collection simply renders nothing. It does not produce an error. Use a surrounding conditional if you want to show an explicit empty state.