Attributes
PHP 8 attributes that add behavior to your component properties and methods.
Overview
LiVue provides a rich set of PHP 8 attributes to declaratively add behavior to your components. They fall into several categories:
| Attribute | Target | Purpose |
|---|---|---|
#[Layout] |
Class | Set Blade layout for page components |
#[Title] |
Class | Set page title for page components |
#[Validate] |
Property | Add Laravel validation rules |
#[Computed] |
Method | Cached derived values |
#[Url] |
Property | Sync with URL query string |
#[Session] |
Property | Persist in server session |
#[Guarded] |
Property | Protect from client modification |
#[Vue] |
Method | Client-side JavaScript execution |
#[Json] |
Method | Isolated request + validation errors in Promise reject |
#[Renderless] |
Method | Skip server re-render |
#[On] |
Method | Declare event listener |
#[Isolate] |
Class | Independent AJAX requests |
#[Lazy] |
Class | Load on viewport entry |
#[Defer] |
Class | Load after page render |
#[Reactive] |
Property | Auto-sync from parent |
#[Modelable] |
Property | Two-way parent/child binding |
#[Confirm] |
Method | Require user confirmation |
#[TabSync] |
Class | Sync state across browser tabs |
#[Transition] |
Method | View Transitions API animations |
#[ObservedBy] |
Class | Attach lifecycle event observer |
| Traits | ||
UsePagination |
Trait | Laravel pagination with URL sync |
WithDirtyTracking |
Trait | Track property modifications |
#[Layout] & #[Title]
Used on page components to specify the Blade layout and page title.
use LiVue\Attributes\Layout;
use LiVue\Attributes\Title;
#[Layout('layouts.admin')]
#[Title('Dashboard - Admin')]
class Dashboard extends Component
{
protected function render(): string
{
return 'livue.dashboard';
}
}
Both can also be placed on the render() method for per-render overrides. Title priority: render() attribute > class attribute > $title property > config('app.name').
#[Validate]
Add Laravel validation rules to properties. Call $this->validate() to trigger validation.
use LiVue\Attributes\Validate;
class ContactForm extends Component
{
#[Validate('required|min:3')]
public string $name = '';
#[Validate('required|email')]
public string $email = '';
public function submit()
{
$this->validate();
Contact::create([...]);
}
}
Display errors in templates via $errors:
<input v-model="name" />
<span v-if="$errors.name" class="text-red-500">
{{ $errors.name }}
</span>
For real-time validation, use $this->validateOnly('field') in updated{Property} hooks. Custom messages via #[Validate('rule', message: '...')] or a messages() method.
See State Management for full validation details.
#[Computed]
Create cached, derived values from your component state. The result is cached for the duration of a single request.
use LiVue\Attributes\Computed;
class Cart extends Component
{
public array $items = [];
#[Computed]
public function total(): float
{
return collect($this->items)
->sum(fn($item) => $item['price'] * $item['quantity']);
}
}
Access in templates as $this->total. To recalculate within the same request, use unset($this->total).
persist |
Cache across requests (default: false) |
seconds |
Cache TTL in seconds (default: 3600) |
cache |
Share cache across all instances (default: false) |
#[Url]
Sync component properties with the URL query string. When the property changes, the URL updates; when users navigate to the URL, the property is initialized from it.
use LiVue\Attributes\Url;
class UserList extends Component
{
#[Url]
public string $search = '';
#[Url(as: 'q', history: true)]
public string $query = '';
#[Url(except: '')]
public string $filter = '';
}
as |
Custom URL parameter name (default: property name) |
history |
Use pushState instead of replaceState (default: false) |
keep |
Always show in URL even when empty (default: false) |
except |
Value to exclude from URL (keeps URL clean) |
#[Session]
Persist property values in the Laravel session across page loads. Values are read on mount and saved on dehydrate.
use LiVue\Attributes\Session;
class SearchPage extends Component
{
#[Session]
public string $search = '';
#[Session(key: 'prefs_{userId}')]
public array $preferences = [];
}
The key parameter supports {property} placeholders for dynamic keys. Default key format: livue.{Component}.{property}.
#[Guarded]
Protect properties from client-side modification. Guarded properties are hidden from JavaScript and encrypted in the snapshot. Server-side code can still modify them.
use LiVue\Attributes\Guarded;
class InvoiceEditor extends Component
{
#[Guarded]
public int $invoiceId;
#[Guarded]
public float $total = 0;
public string $notes = ''; // Modifiable by client
}
Use for IDs, authorization flags, computed totals, and any data that shouldn't be tampered with from the browser.
#[On]
Declare event listeners as method attributes. This is an alternative to the $listeners property. The attribute is repeatable.
use LiVue\Attributes\On;
class Modal extends Component
{
public bool $show = false;
#[On('open-modal')]
public function open(array $data = []): void
{
$this->show = true;
}
#[On('reset')]
#[On('clear')]
public function resetState(): void
{
$this->show = false;
}
}
See Events for dispatching and scoping.
#[Renderless]
Skip HTML re-rendering when a method is called via AJAX. The updated state is still sent to the client, and Vue handles DOM updates reactively. This reduces server load for methods that only change state.
use LiVue\Attributes\Renderless;
class Counter extends Component
{
public int $count = 0;
#[Renderless]
public function increment(): void
{
$this->count++;
// State sent to client, but HTML not re-rendered on server
}
}
| Attribute | Skip Render | Isolated Request | Isolated Errors | Use Case |
|---|---|---|---|---|
| None | No | No | No | Normal methods with re-render |
#[Renderless] |
Yes | No | No | Actions that change state without re-render |
#[Json] |
Yes | Yes | Yes | API-like endpoints with dedicated error handling |
Return values are available as Promises from direct calls (e.g. let result = await save()). #[Json] adds request isolation (bypasses the pool) and validation isolation (errors go to Promise reject, not $errors).
#[Vue]
Execute JavaScript directly in the browser without server round-trips. The method returns a JS string that gets executed client-side.
use LiVue\Attributes\Vue;
class TodoForm extends Component
{
public string $title = '';
#[Vue]
public function resetForm(): string
{
return <<<'JS'
state.title = '';
JS;
}
}
Inside the JS code you have access to state (reactive Vue state), livue (helper), and $el (root DOM element). Use $this->vue("js code") to queue JS execution after a server method.
#[Json]
Mark a method as a JSON endpoint with two key behaviors: isolated requests (bypasses the batching pool, won't block other components) and isolated validation (errors go to the Promise reject, not $errors). Also skips Blade re-rendering like #[Renderless].
use LiVue\Attributes\Json;
#[Json]
public function search(string $term): array
{
$this->validate([
'term' => 'required|min:2',
]);
return Product::where('name', 'like', "%{$term}%")
->take(10)
->get()->toArray();
}
@script
const handleSearch = async () => {
try {
const results = await livue.call(
'search', [query.value]
);
state.results = results;
} catch (error) {
// error.errors = { term: ['...'] }
// $errors is NOT affected
console.log(error.errors);
}
};
@endscript
| Behavior | Description |
|---|---|
| Isolated request | Bypasses the request pool — won't block or be blocked by other component requests |
| Isolated validation | Validation errors go to the Promise catch, not $errors |
| Skip render | No Blade re-rendering (same as #[Renderless]) |
#[Isolate]
Exclude a component from request pooling. By default, LiVue batches simultaneous AJAX requests into a single HTTP call. Isolated components send independent requests, preventing slow components from blocking fast ones.
use LiVue\Attributes\Isolate;
#[Isolate]
class SlowDashboard extends Component
{
public function refresh()
{
// 3-second API call won't block other components
$this->stats = ExternalApi::fetchStats();
}
}
Use for slow API calls, heavy database queries, and components with unpredictable response times.
#[Lazy] & #[Defer]
Both defer component loading for faster initial page renders. #[Lazy] loads when the component enters the viewport; #[Defer] loads immediately after page render.
use LiVue\Attributes\Lazy;
#[Lazy]
class HeavyChart extends Component
{
public function placeholder(): string
{
return '<div class="animate-pulse">Loading...</div>';
}
}
use LiVue\Attributes\Defer;
#[Defer]
class Analytics extends Component
{
public function placeholder(): string
{
return view('placeholders.skeleton');
}
}
| Attribute | Loads When | Best For |
|---|---|---|
#[Lazy] |
Entering viewport | Below-the-fold content |
#[Defer] |
After page load | Above-the-fold heavy content |
#[Lazy(onLoad: true)] |
After page load | Same as #[Defer] |
#[Reactive] & #[Modelable]
Control data flow between parent and child components. #[Reactive] enables one-way sync (parent → child); #[Modelable] enables two-way binding.
// Child component
class TodoList extends Component
{
#[Reactive]
public array $items = [];
}
@livue('todo-list', ['items' => $items])
// Child component
class TextInput extends Component
{
#[Modelable]
public string $value = '';
}
@livue('text-input', ['value' => $name],
['model' => 'name'])
With #[Reactive], when the parent updates $items, the child automatically receives the new value. With #[Modelable], changes flow in both directions.
#[Confirm]
Require user confirmation before executing a method. Shows a browser confirm dialog (or custom dialog if configured).
use LiVue\Attributes\Confirm;
class TaskManager extends Component
{
#[Confirm('Are you sure you want to delete this task?')]
public function delete(int $id): void
{
Task::destroy($id);
}
#[Confirm(message: 'Clear all data?', title: 'Warning', confirmText: 'Yes, clear')]
public function clearAll(): void
{
Task::truncate();
}
}
message |
Confirmation message (default: 'Are you sure?') |
title |
Dialog title (optional) |
confirmText |
Confirm button text (default: 'Confirm') |
cancelText |
Cancel button text (default: 'Cancel') |
#[TabSync]
Enable automatic state synchronization across browser tabs using the BroadcastChannel API.
use LiVue\Attributes\TabSync;
#[TabSync]
class ShoppingCart extends Component
{
public array $items = [];
public float $total = 0;
}
// Sync only specific properties
#[TabSync(only: ['items', 'total'])]
class OrderList extends Component { ... }
// Trigger server refresh on sync
#[TabSync(reactive: true)]
class Counter extends Component { ... }
only |
Sync only these properties (array) |
except |
Exclude these properties from sync (array) |
reactive |
Trigger server refresh when synced (default: false) |
#[Transition]
Configure View Transitions API animations for specific methods. Uses progressive enhancement — unsupported browsers simply skip the animation.
use LiVue\Attributes\Transition;
class Wizard extends Component
{
public int $step = 1;
#[Transition(type: 'forward')]
public function next() { $this->step++; }
#[Transition(type: 'backward')]
public function back() { $this->step--; }
#[Transition(skip: true)]
public function goToStep(int $step) { $this->step = $step; }
}
Types: 'forward' (slide left), 'backward' (slide right), null (crossfade), or any custom string matched by CSS. Use skip: true for instant updates.
#[ObservedBy]
Attach a lifecycle event observer directly on the component class. The attribute is repeatable, so you can attach multiple observers. This follows the same pattern as Eloquent's ObservedBy.
use LiVue\Attributes\ObservedBy;
#[ObservedBy(CounterObserver::class)]
class Counter extends Component
{
// ...
}
// Multiple observers (repeatable)
#[ObservedBy(AuditObserver::class)]
#[ObservedBy(MetricsObserver::class)]
class Order extends Component
{
// ...
}
The observer class defines methods matching lifecycle event names (booting, mounted, calling, etc.). See the Lifecycle page for the full observer pattern and available events.
UsePagination
A trait that integrates Laravel's paginator with LiVue. It tracks the current page, syncs it with the URL automatically, and exposes pagination data and actions as a Vue composable.
use LiVue\Features\SupportPagination\UsePagination;
class UserList extends Component
{
use UsePagination;
protected array $composables = ['usePagination'];
protected function render(): string
{
return 'livue.user-list';
}
}
In your Blade template, call $this->paginate() to paginate a query and $this->links() to render pagination links:
@php $users = $this->paginate(User::query(), 15) @endphp
<div v-for="user in users">
{{ user.name }}
</div>
{!! $this->links() !!}
Methods
| Method | Returns | Description |
|---|---|---|
paginate($query, $perPage) |
LengthAwarePaginator |
Paginate an Eloquent query and store the paginator internally |
links() |
Htmlable |
Render the pagination view using the stored paginator |
setPage($page) |
void |
Set the current page |
nextPage() |
void |
Go to the next page |
previousPage() |
void |
Go to the previous page |
resetPage() |
void |
Reset to page 1 |
Vue Composable Data
When registered as a composable, pagination data and actions are available in your Vue template under the pagination namespace:
| Property | Type | Description |
|---|---|---|
pagination.page |
int |
Current page |
pagination.total |
int |
Total items |
pagination.perPage |
int |
Items per page |
pagination.lastPage |
int |
Last page number |
pagination.hasPages |
bool |
Has more than one page |
pagination.onFirstPage |
bool |
Is on the first page |
pagination.hasMorePages |
bool |
Has more pages after current |
pagination.setPage(page) |
Go to a specific page |
pagination.nextPage() |
Go to the next page |
pagination.previousPage() |
Go to the previous page |
pagination.resetPage() |
Reset to page 1 |
pagination.firstPage() |
Go to the first page |
<button @click="pagination.previousPage()" :disabled="pagination.onFirstPage">Prev</button>
<span>Page {{ pagination.page }} of {{ pagination.lastPage }}</span>
<button @click="pagination.nextPage()" :disabled="!pagination.hasMorePages">Next</button>
WithDirtyTracking
A trait for tracking modifications to component state on the server side. Useful for conditional saves, detecting changed fields, and accessing original values.
use LiVue\Features\SupportDirtyTracking\WithDirtyTracking;
class EditProfile extends Component
{
use WithDirtyTracking;
public string $name = '';
public string $email = '';
public function save(): void
{
if (!$this->isDirty()) {
return; // Nothing to save
}
if ($this->isDirty('email')) {
// Email changed, request verification
}
$this->user->update($this->getDirty());
}
}
Methods
| Method | Returns | Description |
|---|---|---|
isDirty() |
bool |
Any property has been modified |
isDirty('prop') |
bool |
Specific property has been modified |
isClean() |
bool |
No modifications (opposite of isDirty) |
getDirty() |
array |
Array of changes [key => newValue] |
getOriginal() |
array |
All original state values |
getOriginal('prop') |
mixed |
Original value of a specific property |
Custom Attributes
LiVue allows you to create custom PHP attributes with automatic lifecycle behavior. Extend the base Attribute class and your attribute will be automatically discovered — no registration needed.
use Attribute;
use LiVue\Features\SupportAttributes\Attribute as LiVueAttribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class Trim extends LiVueAttribute
{
public function hydrate(): void
{
$value = $this->getValue();
if (is_string($value)) {
$this->setValue(trim($value));
}
}
}
use App\LiVue\Attributes\Trim;
class ContactForm extends Component
{
#[Trim]
#[Validate('required|min:3')]
public string $name = ''; // trimmed BEFORE validation
}
Custom attributes support property, method, and class targets with their own lifecycle hooks. You can also generate them via Artisan: php artisan make:livue-attribute Trim. See Extending LiVue for the full documentation including method attributes, class attributes, and constructor parameters.