v1 LiVue v1 is here — server-driven reactivity for Laravel using Vue.js Get Started →

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).

Parameters
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 = '';
}
Parameters
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
    }
}
Render behavior comparison
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].

Component
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();
}
Template
@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.

#[Lazy] - On viewport
use LiVue\Attributes\Lazy;

#[Lazy]
class HeavyChart extends Component
{
    public function placeholder(): string
    {
        return '<div class="animate-pulse">Loading...</div>';
    }
}
#[Defer] - After page load
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.

#[Reactive] - One-way sync
// Child component
class TodoList extends Component
{
    #[Reactive]
    public array $items = [];
}


@livue('todo-list', ['items' => $items])
#[Modelable] - Two-way binding
// 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();
    }
}
Parameters
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 { ... }
Parameters
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.

UserList.php
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:

user-list.blade.php
@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
Actions
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
Vue template example
<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.

EditProfile.php
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.

app/LiVue/Attributes/Trim.php
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));
        }
    }
}
Usage in a component
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.