K

Forms

Repeater

Repeater manages repeated groups of fields and can persist hasMany relationship data.

Repeater preview
On this page

Repeater manages repeated groups of fields and can persist hasMany relationship data.

Basic Usage

use NyonCode\WireForms\Components\Repeater;
use NyonCode\WireForms\Components\TextInput;
 
Repeater::make('contacts')
->schema([
TextInput::make('name')->required(),
TextInput::make('email')->email(),
])

Relationship Mode

Use relationship() when the repeater should save related records.

Repeater::make('contacts')
->relationship('contacts')
->schema([
TextInput::make('name')->required(),
TextInput::make('email')->email(),
])
->addable()
->deletable()
->reorderable()

Limits and UX Controls

Repeater::make('contacts')
->minItems(1)
->maxItems(10)
->addButtonLabel('Add contact')
->collapsible()
->itemLabel(fn (array $state) => $state['name'] ?? null) // named items: "#1 Ada"

Named items

By default each item block is headed by its number (#1, #2, …). Pass itemLabel() a static string or a closure of the item's state (and index) to show a name next to the number — handy for identifying collapsed items. The label re-renders on the item's reactive cycle, so pair it with a ->live() field to update it as the user types.

Repeater::make('contacts')
->schema([TextInput::make('name')->live()])
->itemLabel(fn (array $state, int $index) => $state['name'] ?? "Contact #{$index}");
Method Purpose
addable() Allow new items
deletable() Allow item removal
reorderable() Allow manual reordering
collapsible() Let users collapse item blocks
collapsed() Start items collapsed
minItems() / maxItems() Constrain collection size
addable(bool) Allow adding new items (default true)
deletable(bool) Allow removing items (default true)
reorderable(bool) Allow drag-to-reorder (default false)
collapsible(bool) Allow collapsing item blocks
collapsed(bool) Start all items collapsed (implies collapsible)
minItems(int|null) Minimum item count
maxItems(int|null) Maximum item count
addButtonLabel(string|null) Label on the add button
itemLabel(string|Closure|null) Name shown next to each item's number (fn(array $state, int $index): ?string)
disabled(bool|Closure) Disable add/delete/reorder controls
mutateRelationshipDataBeforeSaveUsing(Closure) Transform item data before persistence

Table Layout

Short, uniform rows (invoice lines, key/value pairs) read better as a table than as a card per item. table() lays the items out as rows under one header — same state paths, same add/remove/reorder wiring, only the arrangement differs:

Repeater::make('lines')
->table()
->reorderable()
->schema([
TextInput::make('description')->label('What'),
TextInput::make('amount')->label('How much'),
])

Each schema field becomes a column headed by its own label, and the per-cell label is hidden so it is not repeated on every row. Per-item collapsing does not apply to a row, so collapsible() is ignored in this layout.

Per-Item Reactivity

Reactive behavior inside a repeater resolves per item: afterStateUpdated(), live validation, field actions, remote select search and conditional visibility all read the item's own state bag, and $get/$set are scoped to that item.

Repeater::make('contacts')->schema([
Select::make('type')->options(['email' => 'Email', 'other' => 'Other'])->live(),
TextInput::make('other_detail')->visibleWhen('type', 'other'),
])

Here other_detail shows only in the rows whose own type is other — flipping row 2's select never affects row 1. See Reactive Fields for the full accessor reference.

When to Use It

Use Repeater when a single form owns a small to medium collection of related child records and the user should manage them inline.

If the child records need independent filtering, pagination, or heavy workflows, give them their own table or screen.