Directives
Custom Vue directives provided by LiVue for server interaction, timing control, and declarative UI behavior.
v-click
A clean, declarative syntax for calling server-side methods. Instead of writing @click="method()", you can use v-click="method" directly on any element.
Basic Usage
<!-- Call a method with no arguments -->
<button v-click="increment">+1</button>
<!-- Pass a single argument -->
<button v-click="['delete', {{ item.id }}]">Delete</button>
<!-- Pass multiple arguments as an array -->
<button v-click="['update', {{ item.id }}, 'active']">Activate</button>
<!-- Pass a string argument -->
<button v-click="['setStatus', 'published']">Publish</button>
Modifiers
| Modifier | Example | Description |
|---|---|---|
.prevent |
v-click.prevent="save" |
Calls event.preventDefault() |
.stop |
v-click.stop="handle" |
Calls event.stopPropagation() |
.self |
v-click.self="close" |
Only triggers if the event target is the element itself |
.once |
v-click.once="init" |
Handler fires only once, then is removed |
.outside |
v-click.outside="close" |
Triggers when clicking outside the element |
.debounce |
v-click.debounce.500ms="save" |
Debounces the action (default 250ms, customizable) |
.throttle |
v-click.throttle.1000ms="track" |
Throttles the action (default 250ms, customizable) |
.capture |
v-click.capture="handle" |
Listens in capture phase instead of bubbling |
.passive |
v-click.passive="scroll" |
Passive mode, does not block scroll performance |
Combining Modifiers
Modifiers can be chained together on a single directive.
<!-- Prevent default and stop propagation -->
<a href="#" v-click.prevent.stop="submit">Submit</a>
<!-- Close modal on overlay click, not on content click -->
<div class="overlay" v-click.self.prevent="close">
<div class="modal">Content</div>
</div>
<!-- Close dropdown on first outside click -->
<div v-click.outside.once="dismiss">
Dropdown content
</div>
Calling Server Methods
There are three ways to call server methods. All produce the same result:
<button v-click="save">Save</button>
<button @click="save()">Save</button>
Use v-click for simple calls and @click for multiple arguments or inline logic. Use v-click modifiers for debounce/throttle.
Direct Calls with @click
Server methods are available directly in your template — no prefix needed. Use them with any Vue event handler:
<!-- No arguments -->
<button @click="save()">Save</button>
<!-- With arguments -->
<button @click="delete({{ item.id }})">Delete</button>
<!-- Multiple arguments -->
<button @click="update({{ item.id }}, 'active')">Activate</button>
<!-- With inline logic -->
<button @click="if (confirm) archive({{ item.id }})">Archive</button>
<!-- Any Vue event, not just click -->
<input @change="toggleItem({{ item.id }})" type="checkbox">
Important — v-click supports method identifiers, call expressions, and array syntax. Call expressions (for example v-click="setPage(page)") are deferred and executed on the click event.
<!-- All valid -->
<button v-click="save"> <!-- Identifier -->
<button v-click="['save', {{ item.id }}]"> <!-- Array args -->
<button v-click="setPage({{ page }})"> <!-- Call expression -->
<button @click="if (confirm) archive({{ item.id }})"> <!-- Inline logic -->
When to Use Each Syntax
| Syntax | Best for |
|---|---|
v-click="method" |
Simple server calls with optional modifiers (.debounce, .throttle, .confirm). |
v-click="method(arg1, arg2)" |
Server calls with explicit arguments while keeping the same v-click modifiers. |
@click="method()" |
Inline logic or client-side branches, and for any Vue event (@change, @input, etc.). |
v-model
Two-way binding between form inputs and your component's public properties. Standard Vue v-model works out of the box with input, textarea, select, checkboxes, and radio buttons. LiVue extends it with timing modifiers for server synchronization.
Basic Usage
<!-- Text input -->
<input v-model="name" type="text" />
<!-- Textarea -->
<textarea v-model="description"></textarea>
<!-- Select -->
<select v-model="country">
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</select>
<!-- Checkbox -->
<input v-model="agreed" type="checkbox" />
LiVue Modifiers
These modifiers control when and how the input value is synchronized with the server. They work with both native HTML inputs and Vue components (such as Vuetify).
| Modifier | Default | Description |
|---|---|---|
.debounce |
150ms | Delays sync until user stops typing. Customizable: .debounce.500ms |
.throttle |
150ms | Limits sync to at most once per interval. Customizable: .throttle.300ms |
.blur |
— | Syncs only when the input loses focus |
.enter |
— | Syncs only when the user presses Enter |
<!-- Debounce 500ms for search inputs -->
<input v-model.debounce.500ms="search" placeholder="Search..." />
<!-- Throttle 300ms for frequently typed inputs -->
<input v-model.throttle.300ms="liveValue" />
<!-- Sync only on blur -->
<input v-model.blur="username" />
<!-- Sync only on Enter -->
<input v-model.enter="command" />
Under the hood, LiVue transforms v-model.debounce.500ms="search" into v-model="search" v-debounce:search.500ms at runtime, so the standard Vue binding is preserved while the modifier directive handles timing.
v-loading
Show or hide elements, or toggle CSS classes, while an AJAX request is in progress. This is useful for spinners, skeleton screens, and disabling interactive elements during server calls.
Show While Loading
By default, elements with v-loading are hidden and become visible only during a request.
<!-- Show spinner while any request is in progress -->
<div v-loading>
Loading...
</div>
<!-- Hide element while loading -->
<div v-loading.remove>
This content disappears during requests
</div>
Target Specific Actions
You can scope the loading state to a specific server method using the .action modifier.
<!-- Show only when 'save' is running -->
<span v-loading.action="'save'">Saving...</span>
<!-- Show only when 'delete' is running -->
<span v-loading.action="'delete'">Deleting...</span>
<button v-click="save">Save</button>
<button v-click="['delete', {{ item.id }}]">Delete</button>
CSS Class Toggling
Instead of showing or hiding elements, you can toggle CSS classes during loading. This is useful for reducing opacity or adding visual cues without removing elements from the page.
<!-- Add 'opacity-50' class while loading -->
<div v-loading.class="'opacity-50 pointer-events-none'">
Content dims while saving
</div>
<!-- Add class only during a specific action -->
<button v-click="save" v-loading.class.action="'opacity-50'">
Save
</button>
v-poll
Automatically refresh component data at regular intervals. Useful for dashboards, notifications, and any UI that should stay current without user interaction.
<!-- Refresh component every 2.5s (default) -->
<div v-poll>
{{ notifications.length }} new notifications
</div>
<!-- Poll every 5 seconds, calling a specific method -->
<div v-poll.5s="'refreshData'">
...
</div>
<!-- Poll in milliseconds -->
<div v-poll.500ms="'checkStatus'">
...
</div>
<!-- Poll only when element is visible in the viewport -->
<div v-poll.3s.visible="'refresh'">
...
</div>
<!-- Keep polling even when the browser tab is inactive -->
<div v-poll.2s.keep-alive>
...
</div>
| Modifier | Description |
|---|---|
.Xs |
Interval in seconds (e.g. .5s, .10s) |
.Xms |
Interval in milliseconds (e.g. .500ms) |
.visible |
Only poll when the element is visible in the viewport (IntersectionObserver) |
.keep-alive |
Continue polling when the browser tab is inactive |
By default, polling pauses automatically when the tab is hidden to reduce unnecessary server requests. If no method is specified, the component calls $refresh, which re-renders the entire component.
v-init
Call a server method when the component is first mounted. This is useful for deferred data loading — the initial HTML renders instantly, and the heavy data is fetched asynchronously afterward.
<!-- Load data when the component mounts -->
<div v-init:loadData>
<div v-loading>Loading...</div>
<div v-loading.remove>
<ul>
<li v-for="item in items">{{ item.name }}</li>
</ul>
</div>
</div>
The method is called once, immediately after Vue mounts the component. Combine it with v-loading to show a placeholder while the data is being fetched.
v-submit
A form submission handler that calls a server method when the form is submitted. It automatically prevents the default browser submission, so you do not need to add .prevent manually.
<form v-submit:save>
<input v-model="name" type="text" />
<input v-model="email" type="email" />
<button type="submit">Save</button>
</form>
When the user submits the form (via button click or pressing Enter), the save() method on your PHP component is called with all bound properties automatically synced.
v-intersect
Triggers a server method when an element enters the viewport. Built on the browser's IntersectionObserver API. Ideal for infinite scrolling, lazy loading, and analytics triggers.
<!-- Infinite scroll: load more items when this element becomes visible -->
<ul>
<li v-for="item in items">{{ item.name }}</li>
</ul>
<div v-intersect:loadMore>
<span v-loading>Loading more...</span>
</div>
<!-- Trigger only once (e.g. analytics) -->
<div v-intersect:trackView.once>
Section content
</div>
<!-- Trigger when element is 50% visible -->
<div v-intersect:animate.half>
...
</div>
<!-- Trigger when element is fully visible -->
<div v-intersect:markRead.full>
...
</div>
| Modifier | Description |
|---|---|
.once |
Fire the method only once, then stop observing |
.half |
Trigger when 50% of the element is visible |
.full |
Trigger when 100% of the element is visible |
v-navigate
Enables SPA-style navigation for anchor links. When added to an
The browser URL, history, and scroll position are updated automatically. Components wrapped in
Adds an active CSS class to an anchor element when its
Without
Active links automatically receive
Integrates with the browser's View Transitions API to add smooth animations when component content changes. When the server re-renders a component, the old and new states are animated automatically.
When you give a transition a name, you can target it with CSS pseudo-elements for full control over the animation.
LiVue includes default fade and slide animations. The directive automatically respects
Preserves and restores the scroll position of an element across re-renders and navigations. Useful for sidebars, scrollable lists, and any container that should maintain its position when the page updates.
The string identifier is used to store and restore the position. Different elements with the same identifier share the same scroll state. This works across component re-renders and SPA navigations via
Shows or hides elements based on whether the component has unsaved changes. A component is considered "dirty" when its client-side state differs from the last server-confirmed state.
Use
Syncs a reactive property to the server whenever its value changes. This is useful for properties bound with
The path supports dot-notation for nested properties (e.g.
Shows content or modifies elements when the user's device goes offline. Built on the browser's
Drag-and-drop reordering for lists, powered by SortableJS. Supports server-side and client-side modes, drag handles, cross-list transfers (Kanban), and touch devices.
When the value is a string, the drop event calls the named PHP method on the server.
When the value is an array reference (no quotes), the reordering happens locally in Vue without calling the server. The changes are sent on the next sync.
Items can be dragged between lists that share the same
Prevents LiVue from updating the content of an element during server re-renders. The element's inner HTML is preserved as-is. This is essential for third-party widgets, manually managed DOM, or any content that should not be touched by the template swap.
Without
Use Waits for a pause in activity. Each new call resets the timer. Only the last call in the series executes. Best for search inputs and auto-save. Executes immediately, then ignores subsequent calls for the cooldown period. Best for rate-limiting repeated actions like button clicks or scroll tracking.<a> tag, it prevents a full page reload. Instead, LiVue fetches the new page content via AJAX and swaps it into the DOM, preserving any <!-- Just add v-navigate to any anchor -->
<a href="/dashboard" v-navigate>Dashboard</a>
<!-- Works with dynamic hrefs -->
<a :href="'/users/' + user.id" v-navigate>
{{ user.name }}
</a>
<!-- Navigation menu -->
<nav>
<a href="/docs" v-navigate>Docs</a>
<a href="/examples" v-navigate>Examples</a>
<a href="/about" v-navigate>About</a>
</nav>v-current
href matches the current URL. Useful for highlighting the active page in navigation menus.
<!-- Adds 'text-white font-bold' when the URL starts with /docs -->
<a href="/docs" v-navigate v-current="'text-white font-bold'">
Docs
</a>
<!-- .exact: only match when the URL is exactly /docs -->
<a href="/docs" v-navigate v-current.exact="'text-white font-bold'">
Docs Home
</a>
<!-- Object syntax: toggle between active and inactive classes -->
<a href="/docs" v-navigate
v-current.exact="{ active: 'text-white bg-vue/20', inactive: 'text-gray-400 hover:text-white' }">
Docs
</a>
Modifier
Description
.exactOnly apply the class when the URL matches exactly, not just as a prefix
.strictLike
.exact but considers trailing slashes as different paths
Value
Description
'classes'String of CSS classes added when active, removed when inactive
{ active, inactive }Object with
active and inactive class strings that toggle based on match state.exact, a link to /docs will also be marked active when the URL is /docs/v1/directives. With .exact, only an exact path match activates the class.
aria-current="page" for accessibility. The directive uses global event delegation, so it works correctly inside v-transition
<!-- Auto-generated transition name -->
<div v-transition>
Step {{ currentStep }} content
</div>
<!-- Named transition for custom CSS animations -->
<div v-transition="'step-content'">
...
</div>
<!-- Skip transition for this element -->
<div v-transition.skip>
This element will not animate
</div>Custom CSS Animations
::view-transition-old(step-content) {
animation: slide-out-left 0.25s ease-out;
}
::view-transition-new(step-content) {
animation: slide-in-right 0.25s ease-in;
}prefers-reduced-motion and falls back gracefully in browsers that do not support the View Transitions API.
v-scroll
<!-- Preserve scroll position with an identifier -->
<div class="h-96 overflow-y-auto" v-scroll="'sidebar-list'">
<ul>
<li v-for="item in items">{{ item.name }}</li>
</ul>
</div>
<!-- Sidebar navigation that keeps scroll position -->
<aside class="overflow-y-auto" v-scroll="'docs-sidebar'">
...
</aside>v-navigate.
v-dirty
<!-- Show "unsaved changes" indicator when dirty -->
<span v-dirty>
You have unsaved changes
</span>
<!-- Hide element when dirty -->
<span v-dirty:remove>
All changes saved
</span>
<!-- Track a specific property -->
<span v-dirty="'name'">
Name has been modified
</span>v-dirty to display save prompts, enable save buttons, or highlight changed fields. The :remove variant inverts the behavior, hiding the element when there are unsaved changes.
v-watch
v-model that need server-side processing on every change, such as triggering watchers, validation, or dependent field updates.
<!-- Sync when the property changes (debounced 500ms by default) -->
<div v-watch="'data.name'">
<input v-model="data.name" />
</div>
<!-- Custom debounce timing -->
<div v-watch.debounce.300ms="'data.email'">
<input v-model="data.email" />
</div>
<!-- Sync only on blur -->
<div v-watch.blur="'data.username'">
<input v-model="data.username" />
</div>
Modifier
Description
.debounce.XmsCustom debounce timing (default is 500ms)
.blurSync only when the element loses focus
data.section.title). When the watched property changes, livue.sync() is called to send the state diff to the server.
v-offline
navigator.onLine property and online/offline events.
<!-- Show a banner when offline -->
<div v-offline class="bg-red-600 text-white p-4">
You are currently offline. Changes will sync when you reconnect.
</div>
<!-- Hide element when offline (e.g. hide a save button) -->
<button v-offline:remove v-click="save">
Save
</button>
<!-- Add a class when offline -->
<div v-offline.class="'opacity-50 pointer-events-none'">
This section becomes dimmed when offline
</div>
<!-- Disable a button when offline -->
<button v-offline.attr="'disabled'">Submit</button>
Modifier
Description
(default)Toggle visibility: hidden when online, visible when offline
:removeInverse visibility: visible when online, hidden when offline
.classAdd the specified CSS classes when offline
.class.removeRemove the specified CSS classes when offline
.attrAdd the specified HTML attribute when offline
v-sort
Server-Side (Calls PHP)
<ul v-sort="'reorder'">
<li v-for="item in items" :key="item.id" v-sort-item="item.id">
{{ item.name }}
</li>
</ul>public function reorder(int|string $item, int $position): void
{
// $item = the ID from v-sort-item
// $position = new 0-based index
}Client-Side (No Server Call)
<div v-sort="data.gallery">
<div v-for="(file, index) in data.gallery" :key="index" v-sort-item="index">
{{ file.name }}
</div>
</div>Handles and Ignored Elements
<li v-sort-item="item.id">
<!-- Only this handle initiates the drag -->
<span v-sort-handle class="cursor-grab">☰</span>
<span>{{ item.name }}</span>
<!-- This button won't trigger drag -->
<button v-sort-ignore v-click="['delete', item.id]">Delete</button>
</li>Cross-List (Kanban)
v-sort-group.
<!-- TODO list -->
<ul v-sort="'reorderTodo'" v-sort-group="'tasks'" data-livue-sort-id="todo">
<li v-for="task in todoTasks" :key="task.id" v-sort-item="task.id">
{{ task.name }}
</li>
</ul>
<!-- DONE list -->
<ul v-sort="'reorderDone'" v-sort-group="'tasks'" data-livue-sort-id="done">
<li v-for="task in doneTasks" :key="task.id" v-sort-item="task.id">
{{ task.name }}
</li>
</ul>Directives Reference
Directive
Description
v-sortSortable container. String value = server call, array ref = client-side
v-sort-itemMarks a sortable item with its ID or index
v-sort-handleRestricts drag initiation to this element
v-sort-ignoreExcludes this element from triggering a drag
v-sort-groupGroup name for cross-list dragging
Modifiers
Modifier
Description
.XmsAnimation duration (e.g.
.300ms)
.no-animationDisable animation entirely
.horizontalHorizontal sorting axis
v-ignore
<!-- Third-party widget that manages its own DOM -->
<div v-ignore>
<div id="chart-container"></div>
</div>
<!-- WYSIWYG editor content -->
<div v-ignore>
<div class="rich-editor"></div>
</div>
<!-- Embedded map that should not be re-rendered -->
<div v-ignore>
<div id="map"></div>
</div>v-ignore, LiVue replaces the component template on every server response. Any DOM modifications made by JavaScript libraries (charts, editors, maps) would be lost. This directive marks a subtree as protected from the swap.
Action Modifiers
v-click modifiers to add debounce or throttle to server method calls.
<!-- Debounce 300ms -->
<button v-click.debounce.300ms="search">Search</button>
<!-- Throttle 500ms -->
<button v-click.throttle.500ms="increment">+1</button>How They Differ
Debounce
Throttle