Custom Vue components

Using custom Vue components in a Splade app works the same as any other Vue application. Imagine this Counter component, stored in the resources/js/Components folder as Counter.vue:

<template>
<button @click="count++">{{ count }}</button>
</template>
 
<script setup>
import { ref } from "vue";
 
const count = ref(1);
</script>

In the main app.js file, you must import and register the component by passing both a name string and a component definition. If you're using SSR, make sure to import the component in ssr.js as well.

import Counter from "./Components/Counter.vue";
 
createApp({
render: renderSpladeApp({ el })
})
.use(SpladePlugin, {
"max_keep_alive": 10,
"transform_anchors": false,
"progress_bar": true
})
.component('Counter', Counter)
.mount(el);

Instead of calling the component method for each component, you may also use the components key and pass an object:

import Counter from "./Components/Counter.vue";
 
createApp({
render: renderSpladeApp({ el })
})
.use(SpladePlugin, {
"max_keep_alive": 10,
"transform_anchors": false,
"progress_bar": true,
"components": {
Counter,
},
})
.mount(el);

Now you may use the component in a Blade template:

<x-layout>
<div class="text-2xl">
<Counter />
</div>
</x-layout>

Async Components

When you register components in the main app.js, like in the example above, the components will end up in the app.js bundle when you build the assets for production use. You may also use dynamic imports, which will only load a component from the server when needed. Vue has a built-in defineAsyncComponent function to accomplish this.

import { createApp } from "vue/dist/vue.esm-bundler.js";
import { createApp, defineAsyncComponent } from "vue/dist/vue.esm-bundler.js";
 
import Counter from "./Components/Counter.vue";
 
createApp({
render: renderSpladeApp({ el })
})
.use(SpladePlugin, {
"max_keep_alive": 10,
"transform_anchors": false,
"progress_bar": true
})
.component('Counter', Counter)
.component('Counter', defineAsyncComponent(() => import("./Components/Counter.vue")))
.mount(el);

Passing data

If you need to pass data to a Vue component (as a property), you may use the @js directive:

<Cart :products="@js($products)" />

Using Splade inside a Vue component

You may use the the global $splade variable to interact with the Splade core, for example, to visit another page:

<script>
export default {
methods: {
visitCheckout() {
this.$splade.visit("/checkout");
}
},
};
</script>

If you're using the setup attribute on a <script> block, you may use Vue's inject function.

<script setup>
import { inject } from "vue";
 
const Splade = inject("$splade");
 
function visitCheckout() {
Splade.visit("/checkout");
}
</script>

Renderless Vue component

Using renderless Vue components allows you to separate the template from the script. The built-in Splade components are built this way. This allows you to put all the logic in the Vue component and keep the template and styling in Blade.

Let's take the Counter example, extract the increase method and add a render method that exposes the count value and the increase method:

<script>
export default {
data() {
return {
count: 1
};
},
 
methods: {
increase() {
this.count++;
}
},
 
render() {
return this.$slots.default({
count: this.count,
increase: this.increase,
});
},
};
</script>

Now you can use the renderless Vue component in a Blade template, and use the increase method and count value.

<x-layout>
<Counter v-slot="counter">
<button @click="counter.increase" v-text="counter.count" />
</Counter>
<x-layout>

Note that you can't use the {{ counter.count }} Vue syntax to echo out the count value, as the template is rendered first by the Blade engine, and Blade uses the same syntax. You may use the @ symbol to inform Blade the expression should remain untouched. This would result in @{{ counter.count }}. Though this works fine, using the v-text attribute seems neater.

You may also use the destructuring assignment syntax in the slot:

<x-layout>
<Counter v-slot="{ count, increase }">
<button @click="increase" v-text="count" />
</Counter>
<x-layout>