- Overview
- Installation
- Configuration
- Provided Properties
- Provided Methods
- Blade Integration
- Examples
- Live Demo
- Accessibility
Pro Component — This trait requires an Aura UI Pro license.
Overview
The WithAuraForm trait enhances Livewire form components with dirty state tracking, unsaved change warnings, and streamlined validation handling. It monitors which form fields have been modified, prevents accidental navigation away from unsaved changes with a browser confirmation dialog, and provides helper methods for common form patterns like reset, save, and cancel.
Installation
Add the trait to your Livewire component:
use BlueStarSystem\AuraUIPro\Traits\WithAuraForm;
use Livewire\Component;
class EditProfile extends Component
{
use WithAuraForm;
// ...
}
Configuration
Tracked Properties
By default, the trait tracks all public properties on the component. To limit tracking to specific properties, define a $formFields property:
public array $formFields = ['name', 'email', 'bio', 'avatar'];
Validation Rules
Use standard Livewire validation by defining a rules() method or $rules property:
protected function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email,' . $this->userId,
'bio' => 'nullable|string|max:500',
];
}
Provided Properties
| Property | Type | Default | Description |
|---|---|---|---|
isDirty |
bool |
false |
Whether any tracked field has been modified from its original value. |
dirtyFields |
array |
[] |
Array of field names that have been modified. |
originalValues |
array |
[] |
Snapshot of field values at mount time (used for dirty comparison). |
Provided Methods
| Method | Description |
|---|---|
initializeForm() |
Takes a snapshot of current values. Called automatically in mount(). |
isFieldDirty(string $field): bool |
Check if a specific field has been modified. |
getDirtyFields(): array |
Returns array of modified field names. |
getOriginalValue(string $field): mixed |
Returns the original value of a field before modification. |
resetForm() |
Revert all fields to their original values and clear dirty state. |
markAsClean() |
Clear dirty state without reverting values (call after successful save). |
confirmLeave(): bool |
Returns whether a "leave page" confirmation should be shown. |
Blade Integration
The trait works with a small Alpine.js snippet to intercept browser navigation when the form is dirty:
<!-- resources/views/livewire/edit-profile.blade.php -->
<div
x-data="{ dirty: @entangle('isDirty') }"
x-on:beforeunload.window="if (dirty) $event.preventDefault()"
>
<form wire:submit="save">
<div class="space-y-4">
<x-aura::input
label="Full Name"
name="name"
wire:model.live="name"
:error="$errors->first('name')"
/>
<x-aura::input
label="Email"
name="email"
type="email"
wire:model.live="email"
:error="$errors->first('email')"
/>
<x-aura::editor
label="Bio"
name="bio"
wire:model.live="bio"
minHeight="120px"
/>
</div>
<div class="mt-6 flex items-center justify-between">
<div>
@if($isDirty)
<span class="text-sm text-amber-600">You have unsaved changes.</span>
@endif
</div>
<div class="flex gap-3">
<x-aura::button
type="button"
variant="ghost"
wire:click="resetForm"
:disabled="!$isDirty"
>
Discard Changes
</x-aura::button>
<x-aura::button
type="submit"
:disabled="!$isDirty"
>
Save Changes
</x-aura::button>
</div>
</div>
</form>
</div>
Examples
Edit Profile Form
<?php
namespace App\Livewire;
use BlueStarSystem\AuraUIPro\Traits\WithAuraForm;
use Livewire\Component;
class EditProfile extends Component
{
use WithAuraForm;
public string $name = '';
public string $email = '';
public string $bio = '';
public ?string $phone = null;
// Only track these fields for dirty state
public array $formFields = ['name', 'email', 'bio', 'phone'];
public function mount(): void
{
$user = auth()->user();
$this->name = $user->name;
$this->email = $user->email;
$this->bio = $user->bio ?? '';
$this->phone = $user->phone;
// Snapshot current values as "clean" state
$this->initializeForm();
}
protected function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email,' . auth()->id(),
'bio' => 'nullable|string|max:500',
'phone' => 'nullable|string|max:20',
];
}
public function save(): void
{
$this->validate();
auth()->user()->update([
'name' => $this->name,
'email' => $this->email,
'bio' => $this->bio,
'phone' => $this->phone,
]);
// Mark form as clean after successful save
$this->markAsClean();
$this->dispatch('toast', type: 'success', message: 'Profile updated.');
}
public function render()
{
return view('livewire.edit-profile');
}
}
Create Record Form
class CreateCustomer extends Component
{
use WithAuraForm;
public string $name = '';
public string $email = '';
public string $company = '';
public string $phone = '';
public string $notes = '';
public array $formFields = ['name', 'email', 'company', 'phone', 'notes'];
public function mount(): void
{
$this->initializeForm();
}
protected function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:customers,email',
'company' => 'nullable|string|max:255',
'phone' => 'nullable|string|max:20',
'notes' => 'nullable|string|max:1000',
];
}
public function save(): void
{
$this->validate();
$customer = Customer::create([
'name' => $this->name,
'email' => $this->email,
'company' => $this->company,
'phone' => $this->phone,
'notes' => $this->notes,
]);
$this->dispatch('toast', type: 'success', message: 'Customer created.');
$this->redirect(route('customers.show', $customer));
}
public function cancel(): void
{
if ($this->isDirty) {
// The browser beforeunload handler will warn the user
$this->redirect(route('customers.index'));
} else {
$this->redirect(route('customers.index'));
}
}
public function render()
{
return view('livewire.create-customer');
}
}
Showing Dirty Field Indicators
You can highlight individual fields that have been modified:
<div>
<x-aura::input
label="Name"
name="name"
wire:model.live="name"
:error="$errors->first('name')"
:class="$this->isFieldDirty('name') ? 'ring-2 ring-amber-300' : ''"
/>
@if($this->isFieldDirty('name'))
<p class="mt-1 text-xs text-amber-600">
Modified (was: "{{ $this->getOriginalValue('name') }}")
</p>
@endif
</div>
Settings Form with Multiple Sections
class AppSettings extends Component
{
use WithAuraForm;
public string $siteName = '';
public string $siteDescription = '';
public string $contactEmail = '';
public string $timezone = '';
public string $dateFormat = '';
public bool $registrationEnabled = true;
public bool $maintenanceMode = false;
public array $formFields = [
'siteName', 'siteDescription', 'contactEmail',
'timezone', 'dateFormat', 'registrationEnabled', 'maintenanceMode',
];
public function mount(): void
{
$settings = Setting::all()->pluck('value', 'key');
$this->siteName = $settings->get('site_name', '');
$this->siteDescription = $settings->get('site_description', '');
$this->contactEmail = $settings->get('contact_email', '');
$this->timezone = $settings->get('timezone', 'UTC');
$this->dateFormat = $settings->get('date_format', 'Y-m-d');
$this->registrationEnabled = (bool) $settings->get('registration_enabled', true);
$this->maintenanceMode = (bool) $settings->get('maintenance_mode', false);
$this->initializeForm();
}
protected function rules(): array
{
return [
'siteName' => 'required|string|max:255',
'siteDescription' => 'nullable|string|max:500',
'contactEmail' => 'required|email',
'timezone' => 'required|timezone',
'dateFormat' => 'required|string',
'registrationEnabled' => 'boolean',
'maintenanceMode' => 'boolean',
];
}
public function save(): void
{
$this->validate();
$settings = [
'site_name' => $this->siteName,
'site_description' => $this->siteDescription,
'contact_email' => $this->contactEmail,
'timezone' => $this->timezone,
'date_format' => $this->dateFormat,
'registration_enabled' => $this->registrationEnabled,
'maintenance_mode' => $this->maintenanceMode,
];
foreach ($settings as $key => $value) {
Setting::updateOrCreate(['key' => $key], ['value' => $value]);
}
$this->markAsClean();
$this->dispatch('toast', type: 'success', message: 'Settings saved.');
}
public function render()
{
return view('livewire.app-settings');
}
}
Discard Changes with Confirmation
@if($isDirty)
<x-aura::button
type="button"
variant="ghost"
x-on:click="if (confirm('Discard all unsaved changes?')) $wire.resetForm()"
>
Discard Changes
</x-aura::button>
@endif
Live Demo
Demo data resets every 2 hours. Products you create will be visible in other demos until the next reset.
Accessibility
- The "unsaved changes" warning text uses
role="status"witharia-live="polite"to announce state changes. - The browser
beforeunloadconfirmation is triggered only whenisDirtyistrue, preventing accidental data loss. - The "Discard Changes" button is disabled when no changes exist, using
aria-disabled="true". - The "Save" button is disabled when no changes exist, preventing unnecessary submissions.
- Modified field indicators include text descriptions (not just color changes) for screen readers.
- Form validation errors are associated with their inputs via
aria-describedby. - The dirty state count is available for screen reader announcements (e.g., "3 unsaved changes").