> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting started with Vue.js — the essentials for using it with Inertia × Laravel

> An introduction to Vue.js, the JS framework most familiar to Laravel users. From an overview of Options API and Composition API to page components, useForm, and shared data with Inertia v3 × Vue 3.

## What is Vue.js?

Vue.js (Vue for short) is a progressive JavaScript framework for building user interfaces. "Progressive" means you can start small and add features as needed—it supports partial integration into an existing HTML page as well as building large SPAs.

The core of Vue is **reactivity**. When data changes, the DOM updates automatically, so developers don't have to manually manage "when to update which element."

<Info>
  This page describes the combination of Vue 3 and Inertia v3. The Laravel 13 starter kit uses this stack by default.
</Info>

### Options API and Composition API

Vue 3 provides two component-writing styles: **Options API** and **Composition API**.

**Options API** is the traditional style carried over from Vue 2. Define components with an options object containing `data`, `methods`, `computed`, `mounted`, etc.

```vue theme={null}
<!-- Options API example -->
<script>
export default {
    data() {
        return { count: 0 }
    },
    methods: {
        increment() {
            this.count++
        }
    }
}
</script>

<template>
    <button @click="increment">{{ count }}</button>
</template>
```

**Composition API** is the newer style introduced in Vue 3. Combined with the `<script setup>` syntax, you can write more concisely. It offers better logic reuse and works well with TypeScript.

```vue theme={null}
<!-- Composition API (<script setup>) example -->
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
    count.value++
}
</script>

<template>
    <button @click="increment">{{ count }}</button>
</template>
```

<Tip>
  In Inertia × Laravel starter kits, `<script setup>` with the Composition API is the standard. All examples on this page use `<script setup>`.
</Tip>

***

## Vue's position in Laravel

### History

Vue's relationship with Laravel is long, starting when Vue was adopted as the default frontend framework in **Laravel 5.3 (2016)**. At the time, `package.json` included Vue, and there was a sample `resources/js/components/ExampleComponent.vue` component.

```mermaid theme={null}
timeline
    title Laravel and Vue history
    2016 : Laravel 5.3 — Vue adopted as default
    2019 : Laravel 6 — Vue scaffolding split into laravel/ui
    2021 : Laravel 8 — Jetstream + Inertia (Vue) starter kit released
    2022 : Laravel 9 — migration to Vite
    2025 : Laravel 12 — starter kits revamped (Vue / React)
    2026 : Laravel 13 — starter kits with Inertia v3 support
```

**In Laravel 6 (2019)**, authentication scaffolding was split into the `laravel/ui` package, and Vue scaffolding moved along with it. Today, the mainstream approach is to select the Inertia + Vue setup via the `laravel new` starter kit.

Vue is the JS framework Laravel users are most familiar with, and Japanese learning resources are plentiful.

### The current mainstream style: Inertia × Vue

The center of gravity for Vue in Laravel today is **Inertia × Vue**. Inertia enables a "modern monolith" architecture where data flows from Laravel controllers directly to Vue components without designing an API.

```mermaid theme={null}
graph LR
    Browser["Browser"]
    Inertia["Inertia.js<br>(adapter layer)"]
    Laravel["Laravel<br>(controller)"]
    Vue["Vue<br>(page component)"]

    Browser <-->|XHR / full page load| Inertia
    Inertia <-->|Inertia response| Laravel
    Inertia -->|props| Vue
    Vue -->|render| Browser
```

***

## Setup

### Via the starter kit (recommended)

For new projects, using a starter kit is the easiest way.

```shell theme={null}
laravel new my-app
```

Choosing **Vue** in the interactive prompt sets up all of the following automatically:

* `inertiajs/inertia-laravel` (server-side adapter)
* `@inertiajs/vue3` (client adapter)
* `vue` (Vue 3 itself)
* `@vitejs/plugin-vue` (Vite plugin)
* The `HandleInertiaRequests` middleware
* Auth screens like login and registration (already implemented in Inertia + Vue)

### Manual installation

To add it to an existing project, install the server-side and client-side pieces separately.

```shell theme={null}
# Server side (PHP)
composer require inertiajs/inertia-laravel

# Client side (JavaScript)
npm install @inertiajs/vue3 vue
npm install --save-dev @vitejs/plugin-vue
```

Next, add the Vue plugin to `vite.config.js`.

```js theme={null}
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
})
```

Start the Inertia app from `resources/js/app.js`.

```js theme={null}
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'

createInertiaApp({
    resolve: (name) =>
        resolvePageComponent(
            `./pages/${name}.vue`,
            import.meta.glob('./pages/**/*.vue'),
        ),
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mount(el)
    },
})
```

<Info>
  For manual installation details (root template configuration, middleware registration, etc.), see the [Inertia official docs](https://inertiajs.com/installation).
</Info>

***

## Directory structure

In the starter kits, Vue page components live in the `resources/js/pages/` directory.

```
resources/js/
├── app.js             # Inertia app entry point
├── bootstrap.js
├── components/        # Reusable UI components
│   ├── NavBar.vue
│   └── ...
├── layouts/           # Layout components
│   ├── AppLayout.vue
│   └── AuthLayout.vue
└── pages/             # Inertia page components (mirror controller names)
    ├── Auth/
    │   ├── Login.vue
    │   └── Register.vue
    ├── Dashboard.vue
    └── Posts/
        ├── Index.vue
        ├── Create.vue
        └── Show.vue
```

Writing `Inertia::render('Posts/Index', [...])` maps to the component at `resources/js/pages/Posts/Index.vue`.

***

## Vue template syntax

Here are the basic template directives needed to read and write starter kit code.

#### `{{ }}` — variable interpolation

Double curly braces embed a JavaScript value or expression into the template.

```vue theme={null}
<script setup>
const name = 'world'
const count = 3
</script>

<template>
    <p>Hello, {{ name }}!</p>
    <p>Doubled: {{ count * 2 }}</p>
</template>
```

#### `v-if` — conditionals

```vue theme={null}
<template>
    <p v-if="isLoggedIn">Welcome!</p>
    <p v-else-if="role === 'admin'">Logged in as admin</p>
    <a v-else href="/login">Log in</a>
</template>
```

Equivalent to Svelte's `{#if}` or React's ternary operator.

#### `v-for` — list rendering

```vue theme={null}
<template>
    <ul>
        <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
</template>
```

Always specify `:key` for efficient diffing. Corresponds to React's `Array.map()`.

#### `v-model` — two-way binding

Use `v-model` to two-way sync a form element's value with a reactive variable.

```vue theme={null}
<script setup>
import { ref } from 'vue'

const title = ref('')
const agreed = ref(false)
const role = ref('viewer')
</script>

<template>
    <!-- Text input -->
    <input v-model="title" type="text" />
    <p>Typing: {{ title }}</p>

    <!-- Checkbox -->
    <input v-model="agreed" type="checkbox" />
    <p>Agreed: {{ agreed }}</p>

    <!-- Select -->
    <select v-model="role">
        <option value="viewer">Viewer</option>
        <option value="editor">Editor</option>
        <option value="admin">Admin</option>
    </select>
</template>
```

#### `:` (v-bind) and `@` (v-on)

* `:attr="value"` — bind a dynamic value to an HTML attribute (shorthand for `v-bind:attr`)
* `@event="handler"` — register an event listener (shorthand for `v-on:event`)

```vue theme={null}
<template>
    <!-- Dynamic attribute binding -->
    <img :src="imageUrl" :alt="imageAlt" />

    <!-- Event handlers -->
    <button @click="handleClick">Click</button>
    <form @submit.prevent="handleSubmit">...</form>
</template>
```

***

## Page component basics

Inertia page components are ordinary Vue components. Data passed from a Laravel controller arrives as props.

### Controller

```php theme={null}
// app/Http/Controllers/PostController.php
use Inertia\Inertia;
use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('Posts/Index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }
}
```

### Vue page component

```vue theme={null}
<!-- resources/js/pages/Posts/Index.vue -->
<script setup>
import { Link } from '@inertiajs/vue3'

defineProps({
    posts: Object,
})
</script>

<template>
    <div>
        <h1>Posts</h1>
        <article v-for="post in posts.data" :key="post.id">
            <h2>
                <Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
            </h2>
            <p>{{ post.created_at }}</p>
        </article>
    </div>
</template>
```

Just declare props with `defineProps()` and use the data passed from the controller in the template. No REST API definition needed.

***

## The `Link` component

The `<Link>` component provided by `@inertiajs/vue3` routes page transitions via XHR, avoiding a full browser reload.

```vue theme={null}
<script setup>
import { Link } from '@inertiajs/vue3'
</script>

<template>
    <!-- Basic link -->
    <Link href="/posts">Posts</Link>

    <!-- Link with POST method (e.g. delete) -->
    <Link href="/posts/1" method="delete" as="button" type="button">
        Delete
    </Link>

    <!-- Preload (fetch ahead of time on hover) -->
    <Link href="/posts/1" preload>View post</Link>
</template>
```

You write it like a normal `<a>` tag, but Inertia swaps only the page component in the background, giving you an SPA-like feel.

***

## The `Form` component

The `<Form>` component from `@inertiajs/vue3` is the recommended form submission style used in the starter kit auth screens. Specify `action` and `method` as props, and access `errors` and `processing` via `v-slot`.

### Basic usage

```vue theme={null}
<script setup>
import { Form } from '@inertiajs/vue3'
</script>

<template>
    <Form action="/posts" method="post" class="flex flex-col gap-4" v-slot="{ errors, processing }">
        <div>
            <label for="title">Title</label>
            <input id="title" name="title" type="text" required />
            <p v-if="errors.title" class="error">{{ errors.title }}</p>
        </div>

        <div>
            <label for="content">Content</label>
            <textarea id="content" name="content"></textarea>
            <p v-if="errors.content" class="error">{{ errors.content }}</p>
        </div>

        <button type="submit" :disabled="processing">
            {{ processing ? 'Submitting...' : 'Post' }}
        </button>
    </Form>
</template>
```

`v-slot="{ errors, processing }"` is Vue's scoped slot syntax, and the `Form` component computes and provides these values automatically. Form fields use HTML-native `name` attributes rather than `v-model`, and the browser's standard form data collection does the work.

### Starter kit pattern

The starter kit uses [Wayfinder](/en/blog/wayfinder-introduction) to manage routes as objects. `store.form()` returns an object containing the route object's `action` and `method`, which is spread onto `<Form>` with `v-bind`.

```vue theme={null}
<script setup lang="ts">
import { Form } from '@inertiajs/vue3'
import { store } from '@/routes/login'
</script>

<template>
    <Form
        v-bind="store.form()"
        :reset-on-success="['password']"
        v-slot="{ errors, processing }"
        class="flex flex-col gap-6"
    >
        <!-- Form contents -->
    </Form>
</template>
```

Fields listed in `reset-on-success` are automatically reset on successful submission. Use it for fields like password fields that should be cleared after submit.

<Info>
  Without Wayfinder, pass the URL directly like `action="/login"` and it behaves the same.
</Info>

***

## The `useForm` helper

For form handling, use `@inertiajs/vue3`'s `useForm` helper. It cleanly implements form state management, submission, and validation error display.

### Controller side

```php theme={null}
// app/Http/Controllers/PostController.php
class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', 'Post created.');
    }
}
```

### Vue form component

```vue theme={null}
<!-- resources/js/pages/Posts/Create.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
    title: '',
    content: '',
})

function submit() {
    form.post('/posts')
}
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <label>Title</label>
            <input v-model="form.title" type="text" />
            <p v-if="form.errors.title" class="error">{{ form.errors.title }}</p>
        </div>

        <div>
            <label>Content</label>
            <textarea v-model="form.content"></textarea>
            <p v-if="form.errors.content" class="error">{{ form.errors.content }}</p>
        </div>

        <button type="submit" :disabled="form.processing">
            {{ form.processing ? 'Submitting...' : 'Post' }}
        </button>
    </form>
</template>
```

Here are the main properties on the object returned by `useForm`.

| Property / method  | Description                                          |
| ------------------ | ---------------------------------------------------- |
| `form.data`        | The form's data object                               |
| `form.errors`      | Validation errors (access by field name)             |
| `form.processing`  | `true` while submitting (used to disable the button) |
| `form.isDirty`     | `true` if changed from initial values                |
| `form.post(url)`   | Submit via POST                                      |
| `form.put(url)`    | Submit via PUT (for updates)                         |
| `form.delete(url)` | Submit via DELETE                                    |
| `form.reset()`     | Reset the form to initial values                     |

When validation errors are returned, `useForm` displays them while preserving the input values. Combined with `v-model`, it delivers a seamless form experience.

***

## Shared data

Data needed on every page (the logged-in user, flash messages, etc.) is defined in the `share()` method of the `HandleInertiaRequests` middleware.

```php theme={null}
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],
            'flash' => [
                'success' => $request->session()->get('success'),
                'error'   => $request->session()->get('error'),
            ],
        ]);
    }
}
```

Use `usePage()` to access shared data from a Vue component.

```vue theme={null}
<script setup>
import { computed } from 'vue'
import { usePage } from '@inertiajs/vue3'

const page = usePage()

// Access shared data
const user = computed(() => page.props.auth.user)
const flash = computed(() => page.props.flash)
</script>

<template>
    <header>
        <span v-if="user">{{ user.name }}</span>
        <span v-else>Guest</span>
    </header>

    <div v-if="flash.success" class="alert-success">
        {{ flash.success }}
    </div>
</template>
```

<Info>
  Because shared data is included on every request, keep it to the minimum needed. Wrapping in `fn()` for lazy evaluation ensures it's only evaluated when actually accessed.
</Info>

***

## Vue 3 reactivity basics

Here are the Vue 3 reactivity APIs to know when developing with Inertia × Vue.

### `ref` — primitive reactive value

```vue theme={null}
<script setup>
import { ref } from 'vue'

const count = ref(0)
const isOpen = ref(false)

// Access with .value (not needed inside templates)
count.value++
</script>

<template>
    <p>{{ count }}</p>
    <button @click="isOpen = !isOpen">Toggle</button>
</template>
```

### `computed` — computed property

```vue theme={null}
<script setup>
import { ref, computed } from 'vue'

const posts = ref([])

const publishedPosts = computed(() =>
    posts.value.filter(post => post.published)
)
</script>
```

### `onMounted` — post-mount work

```vue theme={null}
<script setup>
import { onMounted } from 'vue'

onMounted(() => {
    console.log('Component mounted')
})
</script>
```

***

## Summary

Vue.js has strong affinity with Laravel, and shines especially in a "modern monolith" configuration through Inertia.

| Element             | Role                                             |
| ------------------- | ------------------------------------------------ |
| Laravel controller  | Routing, data retrieval, validation              |
| `Inertia::render()` | Pass data from controller to Vue component       |
| Vue page component  | Receive props and render UI                      |
| `useForm`           | Form state management, submission, error display |
| `Link` component    | Page transitions without a full reload           |
| `usePage().props`   | Access shared data                               |

Inertia × Vue lets you enjoy both the simplicity of a Laravel backend and Vue's reactive UI. Creating a project from the starter kit gets you started immediately, auth screens included.

<Card title="Inertia.js official docs" icon="book-open" href="https://inertiajs.com">
  See the official documentation for all Inertia v3 features.
</Card>


## Related topics

- [Getting started with React — the essentials for using it with Inertia × Laravel](/en/blog/react-introduction.md)
- [Getting started with Svelte — the essentials for using it with Inertia × Laravel](/en/blog/svelte-introduction.md)
- [Installation](/en/installation.md)
- [⚡ Getting started with Livewire 4 — reactive UIs without JavaScript](/en/blog/livewire-introduction.md)
- [Knowledge required before starting Laravel](/en/true-tutorial.md)
