Forms
Wire Forms
Standalone form system for Laravel Livewire. Works independently or with Wire Table.
Nested rows with add, remove, and reorder controls.
Standalone form system for Laravel Livewire. Works independently or with Wire Table.
Installation
composer require nyoncode/wire-forms
Add to Tailwind content paths:
export default { content: [ // ...current app paths './vendor/nyoncode/wire-core/resources/views/**/*.blade.php', './vendor/nyoncode/wire-forms/resources/views/**/*.blade.php', ]}
How Forms Work
Define a Form schema on your Livewire component, bind it to a state path, and render it with {{ $this->form }}.
Single Form
use NyonCode\WireForms\Forms\Form;use NyonCode\WireForms\Forms\WithForms;use NyonCode\WireForms\Components\TextInput; class CreateUser extends Component{ use WithForms; public ?array $data = []; public function form(Form $form): Form { return $form ->statePath('data') ->model(User::class) ->schema([ TextInput::make('name')->required(), TextInput::make('email')->email()->required(), ]) ->successMessage('User created'); } public function save(): void { $this->form->save(); }}
<form wire:submit="save"> {{ $this->form }} <button type="submit">Create</button></form>
Multi-Form
Methods ending with Form are auto-detected:
class UserSettings extends Component{ use WithForms; public array $profileData = []; public array $passwordData = []; public function profileForm(Form $form): Form { return $form ->statePath('profileData') ->model($this->user) ->schema([ TextInput::make('name')->required(), TextInput::make('bio'), ]); } public function passwordForm(Form $form): Form { return $form ->statePath('passwordData') ->schema([ TextInput::make('current_password')->password()->required(), TextInput::make('password')->password()->required()->confirmed(), TextInput::make('password_confirmation')->password()->required(), ]); } public function saveProfile(): void { $this->profileForm->save(); } public function savePassword(): void { $data = $this->passwordForm->validate(); $this->user->update(['password' => Hash::make($data['password'])]); }}
<form wire:submit="saveProfile"> {{ $this->profileForm }} <button type="submit">Save Profile</button></form> <form wire:submit="savePassword"> {{ $this->passwordForm }} <button type="submit">Change Password</button></form>
Explicit Form Registration
Alternative to auto-detection — return method names exactly as defined:
protected function getForms(): array{ return ['profileForm', 'passwordForm'];}
Model Modes
// Create mode — Form::save() calls User::create($data)$form->model(User::class); // Edit mode — Form::save() calls $user->update($data)$form->model($user); // No model — save() throws, but validate() works$form->model(null);
Introspection:
$form->isCreating(); // true when model is a class string$form->isEditing(); // true when model is an instance$form->getModel(); // Model instance or null
Standalone Usage (without Livewire)
Works for server-side validation and data processing:
$form = Form::make() ->schema([ TextInput::make('name')->required()->maxLength(255), TextInput::make('email')->email()->required(), ]) ->state(['name' => 'John', 'email' => 'john@example.com']); // Validate only$validated = $form->validate(); // throws ValidationException on failure // Validate + save$form->model(User::class)->save();
Form API Reference
Schema & State
->schema(array $components) // field definitions->statePath(string $path) // Livewire property name for state->fill(array $data) // populate state->state(array $data) // alias for fill()->getState(): array // current state->getValidationRules(): array // collected rules->validate(): array // validate and return data
Model & Save
->model(string|Model|null $model) // Eloquent model (class for create, instance for edit)->save(): mixed // full save lifecycle->using(Closure $fn) // custom save callback (replaces default persist)
Save Lifecycle Hooks
->mutateDataBeforeSave(Closure $fn) // fn(array $data): array — transform data before persist->beforeSave(Closure $fn) // fn(array $data): void — runs before persist->afterSave(Closure $fn) // fn(Model|mixed $record): void — runs after persist
Notifications
->successMessage(string|Closure|null $msg) // custom success notification text; Closure receives $record->disableSuccessNotification() // no notification after save
Validation
->validationMessages(array $msgs) // custom validation messages
State
->disabled(bool $disabled = true) // make all fields read-only
Authorization
->authorize(bool $usePolicy = true) // enable model policy auto-resolution (create/update)->authorizeUsing(?Closure $callback) // fn(User $user): bool — custom auth check->canSave(): bool // whether the current user may save->isReadOnly(): bool // true when authorization denies save
When ->authorize() is enabled the form becomes read-only (and hides the save button) if the current user lacks the create or update policy permission on the model.
Introspection
->isCreating(): bool // model is class string->isEditing(): bool // model is instance->getModel(): ?Model // current model instance->getFlatComponents(): array // all components (flat)
Rendering
->toHtml(): string // Blade output(string) $form // __toString()
Factory
Form::make() // static factory via container
Livewire Binding
->livewire(Component $component) // bind to Livewire component
WithForms Trait
The WithForms trait provides:
- Auto-detection — scans for methods ending in
Formand registers them - Lazy resolution — forms are only built when first accessed
- Caching — form instances are cached for the request lifecycle
- Magic property access —
$this->profileFormresolves the form
class MyComponent extends Component{ use WithForms; // Access via: // $this->form → calls form() method // $this->profileForm → calls profileForm() method // $this->settingsForm → calls settingsForm() method}
Field Types
Input Fields
- TextInput — text, email, password, numeric, tel, url
- Textarea — multi-line text
- Select — dropdown, searchable, multiple, relationship
- Checkbox — single checkbox
- CheckboxList — multi-checkbox group
- Radio — radio button group
- Toggle — on/off switch
- DateTimePicker — unified date/time/datetime
- ColorPicker — color selector
- FileUpload — file/image upload
- RichEditor — WYSIWYG editor
- Hidden — hidden field
Layout Components
Display Components
- Placeholder — static text
- Alert — alert message
- Html — raw HTML
- ViewField — custom Blade view
Shared Field API
Every field inherits:
->label(string|Closure $label)->helperText(string|Closure $text)->hint(string|Closure $hint)->hintIcon(string $icon)->required(bool|Closure $required = true)->hidden(bool|Closure $hidden = true)->visible(bool|Closure $visible = true)->disabled(bool|Closure $disabled = true)->size('sm'|'md'|'lg'|'xl')->columnSpan(int|string $span) // grid column span->columnStart(int $start) // grid column start->default(mixed $value) // default value->extraAttributes(array $attrs) // HTML attributes->live() // wire:model.live->debounce(int $ms = 500) // wire:model.blur with debounce->rules(string|array $rules) // Laravel validation rules->validationMessages(array $messages) // custom validation messages