Repeaters
A Repeater is a frame that shows one instance of a component for every item of an array, in the array's order. You need one when a list comes from data rather than from layers placed by hand: a feed, the rows of a table, a set of cards, or any list that grows and shrinks in preview. Also called: repeat grid, collection, list component.
The items live in an array variable (or in an array input of the surrounding component), the look of one item lives in the Template Component, and the Repeater connects the two: each field of an item feeds one input of the component.
Making a Repeater
Where: right-click an instance → Repeat; or right-click a frame → Convert to Repeater.
- Repeat wraps the selected instance in a new Flex frame placed where the instance was, laid out as a column, and makes the instance's component the Template Component. The instance itself goes; the list's first item takes its place.
- Convert to Repeater turns the selected frame into the Repeater. The frame keeps its name, size, fills and strokes. A frame without a layout takes a column Flex layout; a Flex or Grid frame keeps its own layout, gap and padding, which then apply to the items.
- Both commands end by opening the Items bind menu, because a Repeater shows one placeholder item until its items are bound (→ The Repeater section).
- Neither command is offered on an artboard, on a layer inside an instance, or in a document you can only view.
Converting a frame deletes every layer inside it. A frame that holds exactly one instance keeps that instance's component as the Template Component; any other content is gone. Undo brings the layers back.
A Repeater holds nothing but its items: layers cannot be pasted, dropped or wrapped into it, and it cannot be unwrapped. Put a heading, a search field or an add button beside the Repeater, in a parent frame.
The Repeater section
Where: select the Repeater → Properties panel → Repeater.
- Template — the component the Repeater renders once per item. The dropdown lists the components the Repeater can use; a variant of a component set is listed as the set's name followed by the variant's name in parentheses. The button next to it (tooltip Go to Template Component) selects the component. A deleted Template Component keeps rendering, as a deleted component keeps rendering in its instances; Restore deleted component under the dropdown brings it back. A component that contains the Repeater, directly or through an instance of another component, cannot be used: the list shows nothing and the Validation console says so.
- Items — the array the list follows. The field is locked: only its source writes it. Bind it from its bind icon: Bind variable lists the array variables, and New array variable… creates one that already holds one item built from the Template Component's input defaults, so the list keeps its look while you fill in real data. Inside a component, Bind input binds the items to an array input of that component, so each instance can supply its own list (→ Array inputs).
- Writes to — where the list's changes land. Empty, a change goes back the way the items came, and a list that follows an expression variable writes its source array by item id (→ Lists computed from other lists). Bind an array variable here when that is not enough: a list that joins several arrays cannot place an added item, nor can one whose expression reshapes its items, and with Writes to bound the whole list is written to that variable instead.
- Below Items, one row per input of the Template Component holds the field of each item that feeds that input:
$item.title, or$item.owner.namefor a nested field, or$itemfor the whole item. Choosing a Template Component fills the rows for every input whose name matches a field of the first item; so does binding items to a Repeater that has no rows filled yet. An input whose row is empty keeps its default on every item. Text that is not a field path is ignored and the row keeps what it had.
Items
Items are any JSON array; objects with named fields are the usual shape:
[
{ "id": "milk", "title": "Oat milk", "qty": 2, "color": "#8B5CF6" },
{ "id": "bread", "title": "Sourdough", "qty": 1, "color": "#F59E0B" }
]
- The array is edited as text in the Variables panel's JSON editor (→ Arrays and objects). A Set Variable action can replace it in preview (→ In preview).
- A field is converted the way a bound variable is: a number feeds a String input as text, a String input takes any text, a Color input takes any color format or an
[r, g, b, a]array of numbers from 0 to 1. A field that is missing, or that the input's type does not accept, leaves that input on its default for that item; the row in the Repeater section is marked with how many items do not fit, and the Validation console lists it. - Every item needs an identity so that the Repeater can tell items apart when the array changes. An object item's
idfield is its identity when it is a string or a number; a plain string or number is its own identity. An item without anid, and every item that repeats an earlier item's identity, is identified by its position instead, and repeated identities are listed in the Validation console.
A Repeater renders the first 100 items of its array. A longer array is listed in the Validation console and the rest is not shown.
Items on the canvas and in Layers
The canvas shows the list the way the design-time values of the variable produce it, so the Repeater lays out exactly as it will in preview.
- The items cannot be selected, and neither can the layers inside them: a click on an item selects the Repeater. To change how an item looks, edit the Template Component; to change what it shows, edit the data.
- Items take no overrides. Everything an item shows comes from the component and its fields.
- The Layers panel shows no rows for items. The Repeater's own row shows how many items it holds.
- A duplicate, a copy or a paste of a Repeater renders its own items, and copying a Repeater carries its Template Component along the way copying an instance carries its component. Pasting a Repeater into its own Template Component is refused: the paste lands outside the component instead.
In preview
The list follows the session value of its array variable.
- A Set Items action rewrites the list from an expression over
$items, its current array:$items.concat([{ "id": "eggs", "title": "Eggs", "qty": 12 }])adds an item at the end,[{ "id": "eggs", "title": "Eggs" }].concat($items)at the start, and$items.filter((item) => item.id !== "milk")removes one. Repeater names the list; a button beside the list targets it, and an action among the Repeater's own events targets the Repeater itself. The new array goes back to whatever drives the items: the array variable, or the array input of the instance the Repeater sits in (→ Set Items). The variable's own value in the document never changes (→ Variables in preview). - A list that follows an expression variable is still editable: its changes land in the array the expression reads (→ Lists computed from other lists).
- Each item keeps its own session state under its identity: what a viewer checked, hid or typed into an item stays with that item when the array is reordered or other items are added. An item whose identity leaves the array takes its state with it, so the same identity added again starts afresh, as does an item dragged into another Repeater.
- Each item's component runs its own events and behaviors, as an instance does. Actions inside the component reach that one item.
- What the Validation console reports about the items, an open debug console reports too, under the Repeater's name, whenever the session value brings the problem about: an array past 100 items, repeated identities, or a field that does not fit its input.
Reacting to an item
Where: select the Repeater → Interactions tab → Add event (+) → Component Event.
- An Emit Event inside the Template Component reaches the Repeater, which handles it with a Component Event of the same name among its own events, as an instance handles its component's events (→ Component events). The Name field suggests what the Template Component emits.
- In the handler's expressions,
$itemis the item whose instance emitted and$indexits position.$items.filter((card) => card.id !== $item.id)in a Set Items removes the clicked item;$item.titlein a Set Variable records it. The expression field offers these names first. Names starting with$are theirs alone: a variable cannot be named with one (→ Naming). $eventis what started the chain: when the Template Component emits from a Drop,$event.itemis the item that was dropped, while$itemstays the item whose instance emitted (→ Dropping items).- An item's control writes its own field: when the Template Component is a toggle, checkbox, input, color input or slider and its built-in input has a row in the Repeater section, the viewer's click or keystroke on an item writes that field of that item, and the array follows (→ Two-way binding on controls). A field whose parent object the item lacks cannot be written; the control shows the item's value again and an open debug console says why.
Reordering and moving items
Where: select the Repeater → Settings tab → Behaviors → the + button (tooltip Add behavior) → Sortable.
With Sortable on a Repeater, viewers drag items to reorder the list in preview, and the new order is written to the array, the way a Set Items writes it: everything that reads the array follows. The Repeater needs Flex layout, and Mode works as on any Sortable frame (→ Sortable).
- An item dragged into another Repeater that has Sortable moves into that Repeater's array: it leaves one array and joins the other, or, if either change cannot be written, neither. The item takes its fields along and shows with the other Repeater's Template Component and fields.
- Items move only between Repeaters whose Template Component is the same component, or a variant of the same component set: a list of cards takes cards, never a column of a board. An item released anywhere else returns to its place, and a Repeater takes nothing but items.
- A Repeater whose array already holds an item with the same
idrefuses the item. - A list that follows an expression variable sorts too: the item moves in the source array next to its new neighbors, and the items the expression leaves out stay where they were (→ Lists computed from other lists). A view that sorts its items puts the item back in its own order.
- With Writes to bound, the item moves in that array, next to the item it was dropped beside, both found by
id. - Lists nest: when each item of a board holds a Repeater of cards, a press on a card sorts the cards, and a press elsewhere on a column sorts the columns, if they are Sortable too.
A move that cannot be written puts the item back, and an open debug console says why.
Two Repeaters showing filters of one array, such as columns of one task list filtered by status, take a move as a new order of that array: no field changes by itself. A Drop on the target list writes the field, so the item stays in the column it was dropped in (Dropping items).
Dropping items
Where: select the Repeater → Interactions tab → Add event (+) → Drop, once the Repeater has Sortable.
A Drop reads the dragged item as $event.item, its position when the drag started as $event.itemIndex, and where it landed in a Sortable list as $event.dropIndex (→ Drop).
- On a Sortable Repeater, Drop runs once the move is written to the array, and the list keeps showing the drop until the Drop's actions have run. For a board of columns filtered from one
tasksarray by status, the Done list's Drop runs a Set Variable ontaskswithtasks.map((task) => (task.id === $event.item.id ? { ...task, status: "done" } : task)), and the card stays in Done. - Inside a Template Component, a Drop handler that runs Emit Event passes
$eventon: the board's Repeater of columns handles the column's event with the column as$itemand the card as$event.item, and the column component never names the board's data.
Lists computed from other lists
A common shape keeps one array, say tasks, and shows views of it through expression variables: tasks.filter((task) => !task.done) as the open list, the rest as the closed one. A Repeater bound to such a view is editable all the same. A change made in the view is carried to the array the expression reads, matched by item id, so checking a task in the open list writes tasks, and both views recompute:
- Checking, typing or picking in an item writes that field of the item with the same
idin the source array, as long as the field passed through the expression unchanged. A field the expression computed, such as a label built from the title, cannot be written back and the change is refused. - A Set Items that removes items removes them from the source array by
id, on any view that keeps the ids. One that adds items adds them to the source array when the expression reads a single array and passes its items through unchanged; an added item whoseidthe source array already holds is refused, since it would overwrite an item the view does not show. - An added item, and an item a drag or a Set Items moved, goes into its source array next to its neighbors in the view: before the item that follows it there, or after the one before it at the end. The items the view leaves out keep their places.
- An expression that joins several arrays takes changes and removals, each to the array that holds the
id, but cannot place an added item, since nothing says which array it belongs to. Bind Writes to for that list, and additions go there. - Items without an
id, or two items sharing one, cannot be matched, and the change is refused.
A refused change leaves every array as it was, the control shows the item's value again, and an open debug console says what could not be placed.
Detaching a Repeater
Where: right-click the Repeater → Detach Repeater; or the Properties panel header → the three-dot button (tooltip More actions) → Detach.
Detaching turns the Repeater into a plain frame holding one real instance per item it showed, with each item's values assigned on the instance. The frame keeps its id, name, size and layout and no longer follows the variable; the instances are yours to edit, delete or reorder. Undo makes the Repeater again.
Array inputs
A component can expose an Array input (→ Defining inputs). Its only use is the Items of a Repeater inside that component: bind the Repeater's Items to the input, and every instance of the component supplies its own list in the input's row, as JSON. The main component shows the input's default value. An outer Repeater can feed an inner one this way: one field of the outer item, an array, goes into the array input of the inner component, which repeats it.
What the Validation console reports
| Message | Cause | Fix |
|---|---|---|
Repeater has no Template Component; it shows nothing | No component is chosen | Pick a component under Template |
Template Component … contains this Repeater | The component holds the Repeater, directly or through an instance | Pick another component, or move the Repeater out of it |
Item Source is not bound; the Repeater shows one … | Items is bound to nothing | Bind Items to an array variable or an array input |
Item Source holds … items; the Repeater shows the first 100 | The array is longer than the limit | Filter the array with an expression variable, or split the list |
… items repeat an earlier item's id | Several items share one id | Give every item its own id |
Item Field of … does not fit … items | The field is missing on those items, or its value is of another type | Fix the data, point the row at another field, or clear the row so the input keeps its default |
… items have a Kind other than separator or heading | Items of a dropdown's or menu's list carry a Kind the list doesn't know, so they show as ordinary items (→ The items) | Set their kind to separator or heading, or remove it |
Items follow an expression variable and … have no id | The list follows an expression variable and some items lack an id, so a change made in preview cannot be matched to the source array (→ Lists computed from other lists) | Give every item an id |
Writes to is bound and … have no id | Writes to is bound and some items lack an id, so a change made in an item's control, or a moved item, cannot be matched to an item of that array; Set Items still replaces the whole array | Give every item an id |
Related
- Component inputs — the inputs the items feed, and the Array input type.
- Variables — array and object variables and the JSON editor.
- Binding properties to inputs and variables — the bind menu the Items field uses.
- Events and actions reference — Set Variable with an expression.
- Debugging a prototype — the Validation console.