| Install | |
|---|---|
composer require mckenziearts/livewire-unsaved-changes |
|
| Latest Version: | v1.1 |
| PHP: | ^8.3 |
A beautiful, animated "unsaved changes" bar for Laravel Livewire forms. Powered by Alpine.js for instant UI feedback without server round-trips.
composer require mckenziearts/livewire-unsaved-changes
The component uses Tailwind CSS classes. Choose the appropriate method based on your project setup:
If your project already uses Tailwind CSS, simply add the package's views to your source paths in your CSS file:
@import 'tailwindcss';
@source '../../vendor/mckenziearts/livewire-unsaved-changes/resources/views/**/*.blade.php';
This allows Tailwind to scan the component's Blade files and generate the necessary classes automatically.
If your project doesn't use Tailwind CSS, publish the pre-compiled CSS assets:
php artisan vendor:publish --tag="livewire-unsaved-changes-assets"
Then include the CSS in your layout:
<link rel="stylesheet" href="{{ asset('vendor/livewire-unsaved-changes/unsaved-changes.css') }}">
Publish the config file:
php artisan vendor:publish --tag="livewire-unsaved-changes-config"
Publish the translations:
php artisan vendor:publish --tag="livewire-unsaved-changes-translations"
Wrap your form with the <x-unsaved-changes> component and use x-model to bind your inputs:
<x-unsaved-changes :$form save-method="save">
<div class="space-y-4">
<input x-model="form.name" type="text" />
<input x-model="form.email" type="email" />
</div>
</x-unsaved-changes>
In your Livewire component:
<?php
declare(strict_types=1);
namespace App\Livewire;
use Livewire\Component;
class Settings extends Component
{
public array $form = [
'name' => '',
'email' => '',
];
public function mount(): void
{
$this->form = [
'name' => auth()->user()->name,
'email' => auth()->user()->email,
];
}
public function save(array $form): void
{
$this->validate([
'form.name' => 'required|string|max:255',
'form.email' => 'required|email',
]);
auth()->user()->update($form);
$this->form = $form;
session()->flash('success', 'Settings saved!');
}
}
<?php
declare(strict_types=1);
namespace App\Livewire\Forms;
use Livewire\Form;
class SettingsForm extends Form
{
public string $name = '';
public string $email = '';
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email'],
];
}
}
<?php
declare(strict_types=1);
namespace App\Livewire;
use App\Livewire\Forms\SettingsForm;
use Livewire\Component;
class Settings extends Component
{
public SettingsForm $form;
public function mount(): void
{
$this->form->name = auth()->user()->name;
$this->form->email = auth()->user()->email;
}
public function save(array $form): void
{
$this->form->fill($form);
$this->form->validate();
auth()->user()->update($this->form->all());
session()->flash('success', 'Settings saved!');
}
}
<x-unsaved-changes :form="$form->all()" save-method="save">
<input x-model="form.name" type="text" />
<input x-model="form.email" type="email" />
</x-unsaved-changes>
The package includes a trait that provides a default saveChanges method:
declare(strict_types=1);
use ShopperLabs\LivewireUnsavedChanges\Traits\WithUnsavedChanges;
class Settings extends Component
{
use WithUnsavedChanges;
public array $form = [];
public function saveChanges(array $form): void
{
parent::saveChanges($form); // Updates $this->form
// Your save logic here
auth()->user()->update($this->form);
}
}
| Prop | Type | Default | Description |
|---|---|---|---|
form |
array |
[] |
The form data to track |
save-method |
string |
saveChanges |
The Livewire method to call on save |
color |
string |
blue |
Save button color (see available colors below) |
position |
string |
bottom |
Bar position: top or bottom |
prevent-navigation |
bool |
false |
Show browser confirmation when leaving with unsaved changes |
message |
string |
(translated) | Custom message text |
save-label |
string |
(translated) | Custom save button label |
discard-label |
string |
(translated) | Custom discard button label |
The color prop accepts any of the following Tailwind CSS color names:
red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
Position at top:
<x-unsaved-changes :$form position="top">
...
</x-unsaved-changes>
Prevent navigation:
<x-unsaved-changes :$form prevent-navigation>
...
</x-unsaved-changes>
Custom color:
<x-unsaved-changes :$form color="green">
...
</x-unsaved-changes>
Custom labels:
<x-unsaved-changes
:$form
message="You have pending changes"
save-label="Save now"
discard-label="Reset"
>
...
</x-unsaved-changes>
// config/unsaved-changes.php
return [
// Bar position: 'bottom' or 'top'
'position' => 'bottom',
// Show browser confirmation when leaving with unsaved changes
'prevent_navigation' => false,
];
The package includes English and French translations. You can publish and customize them:
php artisan vendor:publish --tag="livewire-unsaved-changes-translations"
This component requires x-model for form bindings. Using wire:model will not work because:
wire:model syncs data with the server (Livewire)wire:model{{-- Correct --}}
<input x-model="form.name" />
{{-- Won't work --}}
<input wire:model="form.name" />
This is by design - the whole point is to avoid server requests until the user saves.
To customize the component markup, publish the views:
php artisan vendor:publish --tag="livewire-unsaved-changes-views"
MIT License. See LICENSE for more information.