<for> Loop Directive #

The <for> directive allows you to iterate over arrays and generate repeated content for each item.

Basic Usage #

<for each="item in items">
  <div class="item">
    <h3>{{item.title}}</h3>
    <p>{{item.description}}</p>
  </div>
</for>

This will generate a div for each item in the items array, with the current item available as item within the loop.

Using the Loop Variable #

The loop variable (e.g., item in the example above) is available within the loop body and can be used with the {{}} signal syntax:

<ul>
  <for each="person in people">
    <li>{{person.name}} ({{person.age}} years old)</li>
  </for>
</ul>

Nested Loops #

You can nest <for> directives to iterate over nested collections:

<for each="category in categories">
  <h2>{{category.name}}</h2>
  <ul>
    <for each="product in category.products">
      <li>{{product.name}} - ${{product.price}}</li>
    </for>
  </ul>
</for>

Condition Format #

The each attribute must follow this format:

itemName in collectionName

Where:

  • itemName is the name for the current item variable
  • collectionName is the path to the array in the state object

Notes and Limitations #

  • The collection must be an array
  • The item variable is only available within the loop body
  • You can access nested properties of the item using dot notation
  • If the collection doesn't exist or isn't an array, an error will be raised during rendering
  • Empty <for> bodies (with no children) are silently skipped - no output is generated

Repeat Reconciliation #

When an @observable array changes on the client, the <for> block reconciles by array position by default. The existing block at index i receives the current item at index i; new tail items are appended and excess tail blocks are removed.

Collection changeRuntime behavior
AppendReuse existing blocks and create the new tail
TruncateReuse the shared prefix and remove the excess tail
Replace or reorderRebind existing blocks at each position

Duplicate values and duplicate attributes are safe. Dynamic attributes never act as hidden keys, so changing their order cannot change reconciliation.

<for each="tag in tags">
  <span class="{{tag.className}}">{{tag.label}}</span>
</for>

Because identity is positional, reordering items does not move their existing subtrees by logical item. Browser-owned state such as focus or an uncontrolled input value remains associated with its position.

Explicit keys #

Use an explicit key when reordered, prepended, or removed items must retain their existing DOM and component-local state:

<for each="item in items">
  <todo-row key="{{item.id}}" title="{{item.title}}"></todo-row>
</for>

For an array of unique strings or finite numbers, key the item itself:

<for each="tag in tags">
  <span key="{{tag}}">{{tag}}</span>
</for>

The key attribute must be on the first concrete element inside <for>. If that element is wrapped by one or more leading <if> directives, put key on the concrete element inside the conditional. A nested <for> owns its own child key. Putting key directly on <if>, <for>, or <outlet> fails with invalid-for-key.

The value must be a single binding to the loop variable or a dot-separated property path rooted at it. Calls, brackets, operators, static values, empty paths, unrelated variables, and key on another regular element fail with the same diagnostic.

key is compiler-only metadata: it does not render into SSR or browser-created HTML and does not become a reactive attribute binding. data-key remains a normal application-visible attribute and does not control repeat identity.

Key values must be unique strings or finite numbers. If an update produces a duplicate or invalid value, WebUI warns once and safely uses positional reconciliation for that update. A later valid update re-establishes keyed identity.