The Repeatable block renders a single child template for every element of a bound data array. Design the row once, and the block stamps it out for every item with that item's data in scope. It supports vertical and horizontal list layouts, CSS grid layouts, optional drag-to-reorder, page-based and scroll-based pagination, and a per-row state collection feature that lets you read every row's inner block values from outside the list.


Tip: Use Repeatable when your rows need a free-form layout you design — contact cards, product tiles, feeds, checklists. Prefer the Table block when users need columns, sorting, filtering, or bulk actions. Prefer Kanban when items must be dragged between groups.
Repeatable List and Repeatable Grid are sibling blocks that use the same rendering engine but ship pre-configured as a list or a two-column grid, respectively. They intentionally omit callable methods, pagination wiring, and the per-row state collection that the base Repeatable offers.
Data Binding
Two properties power everything: the array the block iterates, and the unique key for each row.
Property | Type | Default | Description |
|---|---|---|---|
Data Source ( | array (bindable) | Sample items | The array to repeat — one template instance per element. Usually a binding to a data source query result. |
Primary Key ( | field / Index / expression |
| Unique identity of each row. Required for selection targeting, drag-to-reorder, and scroll-to. Use Index only when order is stable. |
Warning: If your data source returns {{ query1.data.items }}, bind to that path — not {{ query1.data }}. A non-array value renders no rows without any error.
Item Template
The Repeatable renders one template block — a Card by default — once per row. Select it on the canvas or layers panel and edit it like any block. Everything added to the template repeats for every row.
Row-Scoped Bindings
Inside the template, a special set of bindings expose the current row's data. Either dot or bracket notation works — they are the same expression. The builder inserts the bracket form:
{{ repeatable1['context']['item']['name'] }} ✅ correct {{ repeatable1['context']['index'] }} ✅ zero-based row position {{ repeatable1['context']['primaryKey'] }} ✅ the row's resolved key {{ repeatable1.context.item.name }} ✅ dot notation works too {{ repeatable1['currentItem']['name'] }} ❌ no such shorthand
Note: While editing bindings in the builder, the expression picker always previews with the first row's data. At runtime each row correctly receives its own data.
Per-Row Isolation
Every block placed in the template gets its own copy per row automatically:
A sibling reference like
{{ price_input.value }}resolves to this row's copy of the input.A method call on a sibling (e.g., Form submit) targets this row's copy only.
An event handler on an inner block fires for that row only — perfect for per-row automations.
Content Properties
Property | Type | Default | Description |
|---|---|---|---|
Sortable ( | boolean | off | Enables drag-to-reorder. Appears only after a Primary Key is set. Reordering updates |
Drag Handle Block ( | block picker | — (whole row) | Restricts dragging to one block inside the template (e.g., a grip icon). Appears when Sortable is on. |
Auto Select ( | None | First Item | Last Item | None | Automatically selects the first or last row when nothing is selected. Fires On Select Item like a real click. |
Collect Child States ( | boolean | off | Exposes each row's inner block states under |
Add-ons → Pagination ( | add-on | — | Adds page-based or scroll-based loading. Requires wiring your data source's limit/offset to |
Scrollable Container ( | block picker | — | The container block whose scrolling drives pagination. Appears when pagination is configured. |
Scrollable (mobile apps only) | toggle | on | Lets the list scroll on the phone. On Pull Down only fires on a scrollable list. |
Bounce (mobile apps only) | toggle | on | Lets the list bounce at either end. Pull-to-refresh needs a scrollable list with Bounce on. |
Header (mobile apps only) | slot | — | A slot for content shown at the top of the list. |
Appearance Properties
Property | Type | Default | Description |
|---|---|---|---|
Layout ( | List | Grid | List | Stack rows as a list or arrange them in a CSS grid. |
Direction ( | Vertical | Horizontal | Vertical | List flow direction. Only appears when Layout is List. |
Columns ( | number | 3 when unset | Grid column count. Only appears when Layout is Grid. Columns are fixed width — not responsive. |
Separator → Show ( | toggle + styles | off | A divider between rows with color and size options. Only when Layout is List. |
Styles ( | style set | gap | Gap between rows, height, padding, overflow, border. |
Visibility ( | condition | always visible | Show or hide the whole block based on a condition. |
Pagination
Pagination is an add-on that loads rows in chunks. It comes in two modes:
Page-Based vs Scroll-Based Pagination
Page-Based: A numbered pager bar renders below the rows. Bind your data source's limit/offset inputs to {{ id.pagination.pageSize }} and {{ id.pagination.offset }}. Bind Total Records to show the page bar (required).
Scroll-Based (Infinite): More rows load as the user nears the list bottom. Same wiring required. Also unlocks the On Pull Down event for pull-to-refresh.
Warning: Binding a static 200-element array with page size 30 renders all 200 rows. Pagination only works when the data source itself consumes the block's page size and offset and returns one page at a time.
Pagination State Keys
| State Key | Type | Description |
| {{ id.pagination.pageSize }} | number | Rows per page — bind your data source's limit input to this |
| {{ id.pagination.offset }} | number | Rows to skip — bind your data source's offset input to this |
| {{ id.pagination.currentPage }} | number | Zero-based current page |
| {{ id.pagination.total }} | number | Total record count (from your data source) |
Events
| Event | Trigger | Payload |
| On Select Item | A row is clicked, or auto-select picks one | selectedItem, selectedItemKey, selectedItemIndex |
| On Order Change | User drops a dragged row in a new position | New order in {{ id.order }} |
| On Pull Down | User pulls the list down from the top | — (scroll-based pagination or mobile) |
Methods
Call these with the Trigger Component Method action: pick the Repeatable as the block and choose the method.
| Method | Params | Description |
| selectNextItem | — | Moves selection to the next row and scrolls it into view. Requires a Primary Key. |
| selectPreviousItem | — | Moves selection to the previous row. Requires a Primary Key. |
| scrollTo | key | Scrolls to the row with the given primary key or index. |
| resetOrder | — | Discards user drag-reordering and restores data order. |
| goToPage | pageNumber | Jumps the pager. Note: loads all pages from 1 through the target. |
| resetPageOffset | — | Collapses scroll-loaded pages back to the first and refreshes. |
| resetInstances | — | Clears all collected per-row inner block states back to template defaults. |
| clearSelection | — | Clears the selected item. With Auto Select active, a new selection is immediately made. |
Exposed State
| State Key | Type | Description |
| {{ id.selectedItem }} | object | The currently selected row's data |
| {{ id.selectedItemKey }} | string | The selected row's primary key |
| {{ id.selectedItemIndex }} | number | The selected row's position |
| {{ id.order }} | array | Row keys in their current (possibly user-dragged) order |
| {{ id.currentData }} | array | Snapshot of the bound data — populated when data comes from a data source |
| {{ id.instances }} | array | Per-row inner block states — only when Collect Child States is on |
| {{ id.context.item }} | object | Current row data — inside template only |
| {{ id.context.index }} | number | Zero-based row position — inside template only |
| {{ id.context.primaryKey }} | string | Row's primary key — inside template only |
The Shape of instances
When Collect Child States is on, each element of {{ id.instances }} holds currentIndex, currentPrimaryKey, and one entry per direct child block. Three special lookups are also available:
instances.byKey(key)— returns a row's full aggregate by its primary keyinstances.indexOfKey(key)— returns a row's position by primary keyinstances.keys— returns every row's primary key as a plain array
{{ repeatable1.instances.keys.map(key => repeatable1.instances.byKey(key).checkbox1.value) }} → [true, false, true] // one entry per row's checkbox1
Repeatable List & Repeatable Grid
Warning: Repeatable List and Repeatable Grid show the Pagination add-on and the Collect Child States toggle in the properties panel, but neither works on these blocks. Paging can't be driven, and instances isn't filled. If you need either feature, use the base Repeatable block.
These are pre-configured siblings that use the same engine as Repeatable but with different defaults and reduced capabilities:
| Feature | Repeatable | Repeatable List | Repeatable Grid |
| Default layout | list (configurable) | list (default, switchable) | grid, 2 columns (default, switchable) |
| Callable methods | Yes (full set) | None | None |
| Working pagination | Yes | No (UI shows but inert) | No (UI shows but inert) |
| Collect Child States / instances | Yes | No (UI shows but inert) | No (UI shows but inert) |
| context.index / context.primaryKey | Yes | No (context.item only) | No (context.item only) |
| Starter card | Simple text card | Rich gradient card (web) / user row (mobile) | Gradient card grid (web) / product card grid (mobile) |
Note: Repeatable List and Grid are excellent for quick mockups and simple displays. For any flow that needs pagination, per-row state reading, or imperative control (next/previous selection, scroll-to), use the base Repeatable block.
Nesting Repeatables
A Repeatable's template can contain another Repeatable — ideal for 2D data like orders with line items:
// Inner Data Source binding: {{ outerRepeatable['context']['item']['lineItems'] }} // Reading inner row state from outside both: {{ outerRepeatable.instances[0].innerRepeatable.instances[1].skuInput.value }}
Behavior & Gotchas
⚠ Sortable toggle is hidden until a Primary Key is set
The Sortable toggle does not appear in the panel until a Primary Key is chosen. Set a Primary Key first, then the toggle becomes available.
⚠ Enabling pagination silently disables Collect Child States
Saving with a pagination add-on forces Collect Child States off. Paginated lists recycle off-screen rows, so per-row state collection would be partial. Choose one or the other per Repeatable.
⚠ Selection clears when the row disappears from data
When the selected row's key no longer exists (a refetch removed it), the selection resets automatically. With Auto Select active, a new item is immediately selected and On Select Item fires.
⚠ New rows land at the bottom of a user-sorted list
After a drag-reorder, incoming rows are appended after the remembered order, not at their natural data position. Call resetOrder to restore data order.
⚠ Per-row state persists across reuse
Collected instances state persists until cleared. A Drawer or modal that reuses the same Repeatable shows previous open's row state (e.g., ticked checkboxes). Call resetInstances on the Drawer's open event.
⚠ Mobile limitations
In mobile apps: drag-to-reorder and the numbered page bar are not available. Lists use infinite scroll and pull-to-refresh instead. The methods selectNextItem, selectPreviousItem and resetOrder do not work on mobile (their error branch runs). The methods scrollTo (scrollable lists), goToPage, clearSelection, resetPageOffset and resetInstances do work.
Common Patterns
Card List with Selection Drawer
Bind data and set primary key: Set Data Source to your query result and Primary Key to the record's
idfield.Design the template card: Open the template card and bind its blocks using row-scoped pills:
{{ repeatable1['context']['item']['name'] }}.Wire On Select Item: On On Select Item, open a Drawer bound to
{{ repeatable1.selectedItem }}.
Paginated Product Grid
Set layout to grid: Set Layout to
gridand pick a column count (e.g., 3).Add pagination add-on: Enable the Pagination add-on, set type to Page based, and bind the data source's limit/offset inputs to
{{ id.pagination.pageSize }}and{{ id.pagination.offset }}.Bind Total Records: Bind Total Records to the data source's count output. The page bar appears once this is set.
Sortable Checklist
Enable sorting: Set a Primary Key, then toggle Sortable on. Optionally pick a Drag Handle Block inside the template.
Persist order: On On Order Change, persist
{{ repeatable1.order }}to your backend.Add a reset button: Offer a "Reset Order" button that calls the
resetOrdermethod.
Related Blocks
Block | Relationship |
|---|---|
Repeatable Grid | Same engine, pre-configured for grid layout |
Repeatable List | Same engine, pre-configured for list layout |
Prefer for dense records with columns, sorting, filtering, bulk actions | |
Prefer when items are dragged between groups/lanes | |
For hierarchical, expandable/collapsible parent-child data |