Є три рівні складності - від простої обгортки до окремого Livewire-компонента.
1. Blade-компонент-обгортка над справжнім <input> - найчастіший випадок (мітка, поле, помилка в одному місці):
{{-- resources/views/components/input-text.blade.php --}}
@props(['name', 'label'])
<label>
{{ $label }}
<input type="text" name="{{ $name }}" {{ $attributes }}>
</label>
@error($name) <p class="text-red-600">{{ $message }}</p> @enderror
<x-input-text name="title" label="Заголовок" wire:model.live.blur="title" />
Секрет - {{ $attributes }}: wire:model з усіма модифікаторами переходить на справжнє поле. Для зручності з $attributes->wire('model') можна прочитати назву властивості й використати її для @error без окремого name.
2. Поле без нативного <input> на Alpine (лічильник, перемикач, вибір кольору) - x-modelable:
{{-- resources/views/components/input-counter.blade.php --}}
<div x-data="{ count: 0 }" x-modelable="count" {{ $attributes }}>
<button type="button" x-on:click="count--">-</button>
<span x-text="count"></span>
<button type="button" x-on:click="count++">+</button>
</div>
<x-input-counter wire:model="quantity" />
<x-input-counter x-model="quantity" /> {{-- працює і в чистому Alpine --}}
x-modelable каже Alpine, яку змінну пов'язати з wire:model чи x-model на цьому елементі. Стан живе в браузері, сервер отримує значення разом з дією.
3. Окремий Livewire-компонент - #[Modelable], коли полю потрібна серверна логіка (пошук по базі для автодоповнення, завантаження варіантів):
use Livewire\Attributes\Modelable;
new class extends Component {
#[Modelable]
public ?int $value = null;
public string $search = '';
#[Computed]
public function options() { /* пошук у базі за $search */ }
};
<livewire:user-picker wire:model="assigneeId" />
Що обрати: перший варіант - за замовчуванням; другий - для суто клієнтської взаємодії без запитів; третій - лише коли справді потрібен сервер, бо кожне поле-компонент додає власний стан, запити й складність синхронізації з батьком.
Пастки: забутий {{ $attributes }} (і wire:model мовчки не працює); type="button" на кнопках усередині форми; wire:key для таких полів у циклах.