Rendering
Blade directives, component tag syntax, inline Vue Composition API with @script, and Teleport support.
Blade Directives
LiVue provides several Blade directives to render components and manage their assets in your layouts and views.
@livue
The primary directive for rendering a LiVue component. Pass the component name as the first argument and an optional array of props as the second.
<!-- Basic usage -->
@livue('counter')
<!-- With props -->
@livue('counter', ['count' => 10])
<!-- With dynamic props -->
@livue('user-profile', ['userId' => $user->id])
@livueScripts
Loads the LiVue JavaScript runtime bundle along with any component-declared scripts (#[Js]). Place it before the closing </body> tag.
<body>
{{ $slot }}
@livueScripts
</body>
@livueStyles
Injects the CSS declared by components via #[Css] attributes into the <head>.
<head>
<meta charset="utf-8">
@livueStyles
</head>
@livueLoadStyle
Loads a stylesheet previously registered with ->onRequest(). Use it inside the Blade view that actually needs the asset.
Signature: @livueLoadStyle('id', 'package') where package is optional (default: app).
@livueLoadStyle('table-css', 'primix/tables')
@livueLoadStyle('app-only-css')
@livueLoadScript
Loads a JavaScript asset registered with ->onRequest().
Signature: @livueLoadScript('id', 'package', options) where package defaults to app.
Supports optional script attributes (type, defer, async).
@livueLoadScript('table-js', 'primix/tables')
@livueLoadScript('app-only-js')
@livueLoadScript('table-js', 'primix/tables', [
'type' => 'module',
'defer' => false,
'async' => true,
])
Execution timing — On full page responses, LiVue hoists on-request loaders to <head> so CSS starts early. During SPA navigation, loaders stay in the swapped body and are replayed by the runtime before component reboot.
@livueHead
Renders dynamic head elements managed by your page components, such as meta tags, canonical URLs, and JSON-LD structured data. These are defined via the $head property or the head() method on your component.
<head>
<meta charset="utf-8">
@livueHead
@livueStyles
</head>
Complete Layout Example
Here is the recommended layout structure with all LiVue directives in place:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@livueHead
@livueStyles
<!-- livue-assets -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
{{ $slot }}
@livueScripts
</body>
</html>
Tip — When inject_assets is enabled in config/livue.php (the default), LiVue automatically injects scripts and styles when it detects components on the page. In that case, the manual Blade directives are optional.
Component Tag Syntax
As an alternative to the @livue directive, you can render components using a self-closing HTML-like tag syntax. Both approaches produce identical output and can coexist in the same template.
<!-- Basic component -->
<livue:counter />
<!-- Static props (passed as strings) -->
<livue:counter count="10" label="My Counter" />
<!-- Dynamic props (PHP expressions) -->
<livue:counter :count="5" />
<livue:counter :count="$initialCount" />
<livue:counter :items="$collection->toArray()" />
<!-- Boolean props -->
<livue:toggle active />
Static vs Dynamic Props
| Syntax | Value Passed | Example |
|---|---|---|
prop="value" |
String 'value' |
count="10" passes '10' |
:prop="expr" |
PHP expression | :count="10" passes integer 10 |
prop |
Boolean true |
active passes true |
Directive vs Tag Comparison
Both syntaxes are equivalent. The tag compiler converts <livue:*> tags into @livue() calls before Blade compilation.
@livue('counter', ['count' => 5])
<livue:counter :count="5" />
Special Options
The tag syntax supports special attributes for advanced component features:
<!-- Component ref for parent access -->
<livue:child ref="myChild" />
<!-- Two-way binding with #[Modelable] -->
<livue:search-input :value="$query" model="search" />
@script Setup
The @script / @endscript directives let you write Vue Composition API logic directly inside your Blade templates. The code runs in the setup() function of the dynamic Vue component, with full access to reactive state refs, the livue helper object, and standard Vue composables.
<div>
<p>Name: <strong v-text="name">{{ $name }}</strong></p>
<p>Greeting: <strong v-text="greeting"></strong></p>
<p>Local count: <strong v-text="localCount"></strong></p>
<button @click="incrementLocal()">+1 Local</button>
<button @click="increment()">+1 Server</button>
</div>
@script
const greeting = computed(() => 'Hello, ' + name.value + '!');
const localCount = ref(0);
function incrementLocal() {
localCount.value++;
}
watch(name, (newVal) => {
console.log('Name changed to:', newVal);
});
onMounted(() => {
console.log('Component mounted!');
});
return { greeting, localCount, incrementLocal };
@endscript
Variables and functions returned from the @script block become available in the template alongside your server state. In the example above, greeting, localCount, and incrementLocal are all usable via Vue directives.
Available APIs
Inside @script, you have access to Vue Composition APIs and your component state without any imports:
ref()- Create reactive referencescomputed()- Computed propertieswatch()- Watch changeswatchEffect()- Reactive effectreactive()- Reactive objectsonMounted()- Mount lifecycleonUnmounted()- Unmount lifecyclenextTick()- DOM update timing
name,count, etc. - PHP properties as refssave(),increment(), etc. - Server methods (direct calls)livue- The LiVue helper objectlivue.toggle()- Toggle boolean propertiesstore(),useStore(),livue.useGlobalStore()- Pinia helpers$errors- Validation errors
Quick Store Pattern in @script
Declare stores in PHP, then consume them from @script: use useStore() for component stores and LiVue::createStore() for global stores.
@script
const componentStore = useStore('demo-counter');
const globalStore = livue.useGlobalStore('demo-global-counter');
const appGlobalStore = livue.useGlobalStore('demo-app-counter');
componentStore.count++;
globalStore.count++;
appGlobalStore.count++;
const syncToBackend = () => livue.sync();
return { componentStore, globalStore, appGlobalStore, syncToBackend };
@endscript
Global stores created in AppServiceProvider with LiVue::createStore() are available everywhere via livue.useGlobalStore(name).
Rules
-
1.
Place
@scriptoutside the root<div>, at the end of your template - 2. Return an object with the bindings you want to expose to the template
-
3.
Only one
@scriptblock per template -
4.
Local refs created in
@scriptreset on server re-render. Use Pinia helpers (store(...),useStore(...)) when state must persist on the client.
IDE Support
For syntax highlighting and autocompletion, you can optionally wrap your code in a <script> tag. LiVue strips it automatically:
@script
<script>
// Now your IDE recognizes this as JavaScript!
const greeting = computed(() => 'Hello, ' + name.value);
const localCount = ref(0);
return { greeting, localCount };
</script>
@endscript
Vue Composition API in Blade
Inside @script, all your PHP public properties are available as Vue refs. Combined with the livue helper, you can build powerful interactions that blend server state with client-side logic.
Server State as Refs
Every public property on your PHP component is injected as a Vue ref. Access the value with .value in JavaScript:
@script
// 'name' and 'count' are PHP public properties, available as refs
const greeting = computed(() => 'Hello, ' + name.value + '!');
const doubled = computed(() => count.value * 2);
// Watch server state changes
watch(count, (newVal, oldVal) => {
console.log(`Count changed: ${oldVal} → ${newVal}`);
});
return { greeting, doubled };
@endscript
The livue Helper
The livue object provides methods for interacting with the server and accessing component metadata:
| API | Description |
|---|---|
method(...args) |
Call a server-side PHP method directly |
livue.toggle('boolProperty') |
Toggle a boolean property on the server |
livue.watch('prop', callback) |
Watch a server property for changes |
livue.$el |
Access the component's root DOM element |
livue.errors |
Reactive object containing validation errors (template shorthand: $errors) |
Practical Example
Here is a complete example combining server state, client-side logic, lifecycle hooks, and the livue helper:
class ScriptTest extends Component
{
public string $name = 'World';
public int $count = 0;
public function increment(): void
{
$this->count++;
}
}
<div>
<p>Count: <strong v-text="count">{{ $count }}</strong></p>
<p>Double: <strong v-text="doubleCount"></strong></p>
<p>Local: <strong v-text="localCount"></strong></p>
<input type="text" v-model="name" />
<button @click="increment()">+1 Server</button>
<button @click="incrementLocal()">+1 Local</button>
</div>
@script
const doubleCount = computed(() => count.value * 2);
const localCount = ref(0);
function incrementLocal() {
localCount.value++;
}
watch(name, (newVal) => {
console.log('Name changed to:', newVal);
});
onMounted(() => {
livue.call('loadData');
});
return { doubleCount, localCount, incrementLocal };
@endscript
Tip — Server state (PHP public properties) persists across template swaps because it lives in the LiVue state layer. Local state created with ref() inside @script resets after each server re-render because the Vue component is recreated.
Teleport
The @teleport / @endteleport directives let you render content outside of the component's DOM tree, using Vue 3's built-in <Teleport> component under the hood. This is particularly useful for modals, tooltips, and notifications that need to escape parent container styles.
Basic Syntax
Pass any valid CSS selector as the teleport target:
<!-- Teleport to the document body -->
@teleport('body')
<div class="modal">...</div>
@endteleport
<!-- Teleport to a specific element -->
@teleport('#modal-container')
<div class="dialog">...</div>
@endteleport
<!-- Teleport to an element by class -->
@teleport('.notifications')
<div class="toast">...</div>
@endteleport
Modal Dialog Example
A common use case is rendering a modal dialog at the document body to avoid z-index and overflow issues:
<div>
<button @click="showModal = true">Open Modal</button>
@teleport('body')
<div v-if="showModal"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="bg-white p-6 rounded-lg shadow-xl max-w-md">
<h2 class="text-xl font-bold mb-4">Confirm Action</h2>
<p class="text-gray-600 mb-6">Are you sure you want to proceed?</p>
<div class="flex gap-3 justify-end">
<button @click="showModal = false">Cancel</button>
<button @click="confirm()">Confirm</button>
</div>
</div>
</div>
@endteleport
</div>
@script
const showModal = ref(false);
return { showModal };
@endscript
Why Teleport?
- 1. Z-index management — Teleported content avoids z-index stacking issues from parent containers
-
2.
CSS isolation — Styles like
overflow: hiddenon parent containers do not affect teleported content - 3. Clean DOM structure — Keep modals and overlays at the document root for better accessibility
- 4. Full reactivity — Teleported content maintains full reactivity with the component state, methods, and computed properties
How It Works
The @teleport directive compiles to Vue's native <Teleport> component:
@teleport('body')
<div>Content</div>
@endteleport
<Teleport to="body">
<div>Content</div>
</Teleport>
Tip — The teleport target element must exist in the DOM when the component mounts. You can use v-if inside @teleport for conditional rendering while keeping the teleport wrapper always present.