Data Binding
Two-way data binding with diff:model keeps your component properties in sync with form inputs.
Basic Usage
Bind an input to a component property:
<input type="text" diff:model="username">Component:
public string $username = '';When the user types, $username updates automatically. When $username changes server-side, the input updates.
Supported Elements
Text Input
<input type="text" diff:model="name">
<input type="email" diff:model="email">
<input type="password" diff:model="password">
<input type="number" diff:model="age">Textarea
<textarea diff:model="description"></textarea>Checkbox
<input type="checkbox" diff:model="active">Component:
public bool $active = false;Radio Buttons
<input type="radio" diff:model="status" value="active"> Active
<input type="radio" diff:model="status" value="inactive"> InactiveComponent:
public string $status = 'active';Select
<select diff:model="category">
<option value="all">All</option>
<option value="active">Active</option>
<option value="archived">Archived</option>
</select>Multiple Select
<select multiple diff:model="tags">
<option value="php">PHP</option>
<option value="laravel">Laravel</option>
<option value="javascript">JavaScript</option>
</select>Component:
public array $tags = [];Modifiers
.lazy
Syncs on change event instead of input:
<input diff:model.lazy="search">Use case: Sync when input loses focus, not on every keystroke.
.live
Syncs with server immediately on every input:
<input diff:model.live="search">Use case: Real-time search or filtering. Without .live, model only updates local state.
.debounce.
Waits X milliseconds after user stops typing (requires .live):
<input diff:model.live.debounce.300="search">Use case: Search inputs to avoid excessive server requests.
Combining Modifiers
{{-- Live binding with 500ms debounce --}}
<input diff:model.live.debounce.500="search">Examples
Live Search
<input
type="text"
diff:model.live.debounce.300="search"
placeholder="Search users...">
<div diff:loading>Searching...</div>
<ul>
@foreach($results as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>Component:
use App\Models\User;
class UserSearch extends Component
{
public string $search = '';
public $results = [];
public function updated(string $property)
{
if ($property === 'search') {
$this->results = User::where('name', 'like', "%{$this->search}%")
->limit(10)
->get();
}
}
}Contact Form
<form diff:submit="submit">
<div>
<label>Name</label>
<input diff:model="name">
<span diff:error="name"></span>
</div>
<div>
<label>Email</label>
<input type="email" diff:model="email">
<span diff:error="email"></span>
</div>
<div>
<label>Message</label>
<textarea diff:model="message"></textarea>
<span diff:error="message"></span>
</div>
<button type="submit" diff:loading.class.opacity-50>
Submit
</button>
</form>Component:
class ContactForm extends Component
{
public string $name = '';
public string $email = '';
public string $message = '';
protected function rules(): array
{
return [
'name' => 'required|min:3',
'email' => 'required|email',
'message' => 'required|min:10',
];
}
public function submit()
{
$this->validate();
// Send email...
// Reset form
$this->reset('name', 'email', 'message');
}
}Filter Component
<div>
<select diff:model.live="category">
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<select diff:model.live="sort">
<option value="name">Name</option>
<option value="price">Price</option>
</select>
<label>
<input type="checkbox" diff:model.live="inStock">
In Stock Only
</label>
<div>
@foreach($products as $product)
<div>{{ $product->name }} - ${{ $product->price }}</div>
@endforeach
</div>
</div>Component:
use App\Models\Product;
class ProductFilter extends Component
{
public string $category = 'all';
public string $sort = 'name';
public bool $inStock = false;
public $products = [];
public function mount()
{
$this->loadProducts();
}
public function updated(string $property)
{
$this->loadProducts();
}
private function loadProducts()
{
$query = Product::query();
if ($this->category !== 'all') {
$query->where('category', $this->category);
}
if ($this->inStock) {
$query->where('stock', '>', 0);
}
$this->products = $query->orderBy($this->sort)->get();
}
}Multi-step Form
<div>
@if($step === 1)
<div>
<h3>Step 1: Personal Info</h3>
<input diff:model="name" placeholder="Name">
<input diff:model="email" placeholder="Email">
<button diff:click="nextStep">Next</button>
</div>
@elseif($step === 2)
<div>
<h3>Step 2: Address</h3>
<input diff:model="address" placeholder="Address">
<input diff:model="city" placeholder="City">
<button diff:click="previousStep">Back</button>
<button diff:click="nextStep">Next</button>
</div>
@else
<div>
<h3>Step 3: Review</h3>
<p>Name: {{ $name }}</p>
<p>Email: {{ $email }}</p>
<p>Address: {{ $address }}, {{ $city }}</p>
<button diff:click="previousStep">Back</button>
<button diff:click="submit">Submit</button>
</div>
@endif
</div>Component:
class MultiStepForm extends Component
{
public int $step = 1;
public string $name = '';
public string $email = '';
public string $address = '';
public string $city = '';
public function nextStep()
{
$this->step++;
}
public function previousStep()
{
$this->step--;
}
public function submit()
{
// Submit form...
}
}Best Practices
1. Choose the Right Modifier
- Real-time search:
diff:model.live.debounce.300 - Form inputs:
diff:model(updates local state, syncs on change) - Immediate server validation:
diff:model.lazy
2. Type Your Properties
// Good - Type hints ensure data integrity
public string $name = '';
public int $age = 0;
public bool $active = false;
public array $tags = [];
// Avoid - Untyped properties can cause issues
public $name;
public $age;3. Validate User Input
protected function rules(): array
{
return [
'email' => 'required|email',
'age' => 'required|integer|min:18',
];
}4. Use Lifecycle Hooks
public function updated(string $property)
{
if ($property === 'search') {
$this->performSearch();
}
}5. Initialize in mount()
public function mount()
{
$this->email = auth()->user()->email;
}Performance Tips
- Use default model for forms: Only use
.livewhen needed - Add
.debounceto live search: Prevents excessive requests - Lazy load data: Don't fetch data until needed
- Optimize
updated()hook: Only react to relevant properties
Troubleshooting
Model Not Updating
Check property is public:
// Wrong
private string $name;
// Correct
public string $name;Type Errors
Ensure property type matches input:
// Checkbox needs bool
public bool $active = false;
// Number input needs int/float
public int $age = 0;Lost Input Value
Make sure property has default value:
// Good
public string $name = '';
// May cause issues
public string $name;Next Steps
Learn more about working with data:
- Forms - Handle form submissions
- Validation - Validate user input
- Loading States - Show feedback during updates
- Contact Form Example - Complete form example
