Skip to main content
Given a list of data, ForEach renders its children once per item — a card for each user, a badge for each tag, a row for each result. Python’s context manager gives you a handle to the current item, and attribute access chains naturally into dot-path expressions, so member.name in Python becomes {{ _loop_1.name }} in the rendered output.

Basic Usage

Pass ForEach a state key that contains a list. The as variable is a reactive reference to the current item — use dot notation to reach into fields, embed it in f-strings, or pass it directly to components that accept string values. When items are simple values (strings, numbers) rather than objects, the context variable represents the value directly — no dot access needed. Dot notation chains through nested objects the same way you’d expect — project.owner.name reaches into {"owner": {"name": "..."}} without any special syntax.

Destructuring

The as clause supports tuple destructuring — as (i, item) — giving you both the zero-based iteration index and the item, matching Python’s enumerate convention. The index is valuable for two things: displaying position numbers, and constructing state paths that target specific items. When an action like SetState needs to modify one particular item in a list, you need its index to build the path. Use the simpler as item form when you only need the item. as (i, item) is worth reaching for when you need positional display or indexed state mutations like SetState(f"items.{i}.done", True).

Nested Loops

When ForEach loops nest, each level automatically captures $item and $index into uniquely scoped let bindings. This means the outer loop’s variables survive even after the inner loop introduces its own — no manual scoping required. Destructure both levels to get named handles. The outer group’s index (gi) becomes part of the inner loop’s key path, threading the two loops together:
Nested ForEach
from prefab_ui.components import Checkbox, Column, Input
from prefab_ui.components.control_flow import ForEach

with ForEach("groups") as (gi, group):
    with Column():
        Input(name=f"groups.{gi}.name")
        with ForEach(f"groups.{gi}.todos") as (ti, todo):
            Checkbox(name=f"groups.{gi}.todos.{ti}.done")
gi and group reference the outer loop’s index and item; ti and todo reference the inner loop’s. Both sets remain valid inside the inner body because each ForEach stores them in separate let scopes under the hood — group resolves to _loop_1 (bound to the outer $item), while todo resolves to _loop_2 (bound to the inner $item). The Todo List example is a full nested-loop application with inline editing, conditional visibility, and indexed state mutations across both loop levels.

API Reference

ForEach Parameters

key
str
required
The data field containing the list to iterate over. Can be passed as a positional argument.
css_class
str | None
default:"None"
Additional Tailwind CSS classes for the wrapper element.

Protocol Reference

ForEach
{
  "type": "ForEach",
  "children?": "[Component]",
  "let?": "object",
  "key": "string (required)",
  "cssClass?": "string"
}
For the complete protocol schema, see ForEach.