Skip to content

Repeater

Repeater stores state as a list of dictionaries (or a simple list when using .simple()). Each item renders the nested schema with Conduit actions addRepeaterItem, removeRepeaterItem, cloneRepeaterItem, and moveRepeaterItem. Use it for line items, addresses, FAQ entries, and any JSON array of structured rows. Relationship-backed variants belong on Relationship repeater; typed block pickers belong on Builder.

Each variation below includes a detailed explanation, the fluent API to paste into your schema, and light/dark screenshots of the rendered control.

Provide a .schema([...]) of child fields. .default_items(1) seeds one empty row when state is empty. Child state paths are read from each item dict by field name.

app/orbit/resources/example_resource.py
from almasix.orbit.forms import Repeater, TextInput
Repeater.make('items')
.label('Line items')
.schema([
TextInput.make('name').label('Name').required(),
TextInput.make('qty').label('Qty').numeric(),
])
.default_items(1)

Orbit Basic repeater (light)

Orbit Basic repeater (dark)

.simple(field) uses a single child field per row (class or-repeater-simple) instead of a multi-field schema — handy for tag-like lists that still need add/remove chrome.

app/orbit/resources/example_resource.py
Repeater.make('aliases')
.label('Aliases')
.simple(TextInput.make('value').placeholder('Alias'))
.default_items(1)

Orbit Simple repeater (light)

Orbit Simple repeater (dark)

.min_items() / .max_items() emit data attributes and disable Add when at max. .default_items() controls the empty-state seed count.

app/orbit/resources/example_resource.py
Repeater.make('phones')
.label('Phone numbers')
.simple(TextInput.make('number').tel())
.default_items(1)
.min_items(1)
.max_items(3)

Orbit Item limits (light)

Orbit Item limits (dark)

.addable(False) hides the Add button; .deletable(False) hides Remove. .add_action_label() customizes the Add button text.

app/orbit/resources/example_resource.py
Repeater.make('addresses')
.label('Addresses')
.schema([TextInput.make('line1').label('Line 1')])
.add_action_label('Add address')
.deletable(True)

Orbit Add and remove controls (light)

Orbit Add and remove controls (dark)

.cloneable() duplicates an item; .reorderable() adds up/down move buttons; .collapsible() wraps each item in Alpine collapse state with Expand/Collapse.

app/orbit/resources/example_resource.py
Repeater.make('sections')
.label('Sections')
.schema([TextInput.make('heading').label('Heading')])
.cloneable()
.reorderable()
.collapsible()

Orbit Cloneable, reorderable, collapsible (light)

Orbit Cloneable, reorderable, collapsible (dark)

.item_label() accepts a string or callable. Callables receive index, item / state, plus render context — use them to show “Address #2” or a title from the row.

app/orbit/resources/example_resource.py
Repeater.make('contacts')
.label('Contacts')
.schema([TextInput.make('name').label('Name')])
.item_label(lambda index, item=None, **_: (item or {}).get('name') or f'Contact {index + 1}')

Orbit Item labels (light)

Orbit Item labels (dark)

.grid(columns) sets data-grid so CSS can arrange item fields in columns without changing the nested schema API.

app/orbit/resources/example_resource.py
Repeater.make('features')
.label('Features')
.schema([
TextInput.make('title').label('Title'),
TextInput.make('icon').label('Icon'),
])
.grid(2)

Orbit Grid layout (light)

Orbit Grid layout (dark)

.table([...]) adds a table header row and or-repeater-table chrome for spreadsheet-like entry. Column labels are presentation only — field order still comes from .schema().

app/orbit/resources/example_resource.py
Repeater.make('items')
.label('Items')
.table(['Name', 'Qty'])
.schema([
TextInput.make('name'),
TextInput.make('qty').numeric(),
])

Orbit Table layout (light)

Orbit Table layout (dark)

.relationship(name) sets data-relationship and stores the relationship name for hydrate/mutate hooks. Prefer RelationshipRepeater when the field is always relation-backed; use these mutators on either class.

app/orbit/resources/example_resource.py
Repeater.make('comments')
.relationship('comments')
.schema([TextInput.make('body').label('Body')])
.mutate_relationship_data_before_fill(lambda data, **_: data)

Orbit Relationship metadata on Repeater (light)

Orbit Relationship metadata on Repeater (dark)

Closures work on .label(), .helper_text(), .placeholder(), .visible(), .disabled(), .required(), and repeater .item_label() where applicable — see Form closures.