> ## 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 Svelte — the essentials for using it with Inertia × Laravel

> An introductory guide to using Svelte with Laravel + Inertia.js. Covers the compiler-based approach and Svelte 5 runes syntax, plus practical usage of Inertia Svelte hooks like useForm and usePage.

## What is Svelte?

[Svelte](https://svelte.dev/) occupies a unique position among JavaScript frameworks. While React and Vue work as **runtime libraries**, Svelte works as a **compiler**. It converts components to pure JavaScript at build time, so no extra framework code needs to be shipped to the browser.

Svelte's biggest characteristic is that it **doesn't use a Virtual DOM**. When state changes, code Svelte generated at compile time directly updates the DOM. This yields extremely lightweight and fast UIs.

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

### Svelte 5 runes

Svelte 5 (released 2024) introduced a new reactivity system called **Runes**. You declare reactive state using special functions (runes) like `$state`, `$derived`, and `$effect`. Unlike the implicit reactivity of Svelte 4 and earlier, the design is explicit and predictable.

```svelte theme={null}
<script lang="ts">
    let count = $state(0)

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

<button onclick={increment}>{count}</button>
```

<Tip>
  Laravel's Svelte starter kit uses Svelte 5 + TypeScript as the standard. All examples on this page are written in TypeScript (`lang="ts"`).
</Tip>

***

## Svelte's position in Laravel

### History

Svelte's relationship with Laravel is much newer than Vue's or React's; the first official support is **starter kit adoption**.

```mermaid theme={null}
timeline
    title Laravel and Svelte history
    2016 : Svelte 1 released (by Rich Harris)
    2019 : Svelte 3 — redesigned around a compiler-based approach
    2021 : Inertia.js — community Svelte adapter released
    2024 : Svelte 5 — introduces runes syntax
    2026 : Laravel 13 — Svelte starter kit officially added (Inertia v3 support)
```

**With Laravel 13 in 2026**, Svelte was officially added to the starter kits, making Svelte an official frontend option in the Laravel ecosystem. It's treated as equal to Vue and React and is selectable in the `laravel new` interactive prompt.

Svelte is still relatively unknown to many Laravel users, but its **compiler-driven small bundles** and **simple syntax** stand out—users switching from Vue or React are often surprised by how little they need to write.

### The current mainstream style: Inertia × Svelte

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

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

    Browser <-->|XHR / full page load| Inertia
    Inertia <-->|Inertia response| Laravel
    Inertia -->|props| Svelte
    Svelte -->|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 **Svelte** in the interactive prompt sets up all of the following automatically:

* `inertiajs/inertia-laravel` (server-side adapter)
* `@inertiajs/svelte` (client adapter)
* `svelte` + `@sveltejs/vite-plugin-svelte` (Svelte 5 itself and the Vite plugin)
* TypeScript + `svelte-check`
* Tailwind CSS + the shadcn-svelte component library
* The `HandleInertiaRequests` middleware
* Auth screens like login and registration (already implemented in Inertia + Svelte + TypeScript)

### 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/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
```

Next, add the Svelte plugin and the Inertia Vite plugin to `vite.config.ts`.

```ts theme={null}
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import inertia from '@inertiajs/vite'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.ts'],
            refresh: true,
        }),
        svelte(),
        inertia(),
    ],
})
```

Start the Inertia app from `resources/js/app.ts`. The `@inertiajs/vite` plugin handles page auto-resolution and mounting, so only a minimal entry point is needed.

```ts theme={null}
import { createInertiaApp } from '@inertiajs/svelte'

createInertiaApp()
```

<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, Svelte page components live in the `resources/js/pages/` directory.

```
resources/js/
├── app.ts             # Inertia app entry point
├── components/        # Reusable UI components
│   └── ui/            # shadcn-svelte components
├── layouts/           # Layout components
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # Utility functions and Svelte rune modules
├── pages/             # Inertia page components (mirror controller names)
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # TypeScript type definitions
```

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

***

## Basic structure of a Svelte file

A `.svelte` file consists of three blocks: **`<script>`, template, and `<style>`**.

```svelte theme={null}
<script lang="ts">
    // Logic (TypeScript)
    let name = $state('Laravel')
</script>

<!-- Template (HTML-like syntax) -->
<h1>Hello, {name}!</h1>

<style>
    /* Scoped CSS (applies only to this component) */
    h1 {
        color: #ff2d20;
    }
</style>
```

The structure resembles Vue's Single File Components (SFCs), but there's less markup and script to write. Because `<style>` is scoped by default, you don't need to worry about class name collisions.

### Template syntax

#### Variable interpolation and expressions

Inside the template, use `{}` to embed JavaScript values and expressions.

```svelte theme={null}
<script lang="ts">
    let name = $state('world')
    let count = $state(3)
</script>

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

#### `{#if}` — conditionals

```svelte theme={null}
<script lang="ts">
    let isLoggedIn = $state(false)
    let role = $state('editor')
</script>

{#if isLoggedIn}
    <p>Welcome!</p>
{:else if role === 'admin'}
    <p>Logged in as admin</p>
{:else}
    <a href="/login">Log in</a>
{/if}
```

Equivalent to Vue's `v-if` / `v-else` or React's ternary operator.

#### `{#each}` — list rendering

```svelte theme={null}
<script lang="ts">
    type Post = { id: number; title: string }
    let posts = $state<Post[]>([
        { id: 1, title: 'First post' },
        { id: 2, title: 'Second post' },
    ])
</script>

<ul>
    {#each posts as post (post.id)}
        <li>{post.title}</li>
    {/each}
</ul>
```

`(post.id)` specifies the key, used for efficient diffing. Equivalent to Vue's `v-for` or React's `Array.map()`.

#### `bind:` — two-way binding

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

```svelte theme={null}
<script lang="ts">
    let title = $state('')
    let agreed = $state(false)
    let role = $state('viewer')
</script>

<!-- Text input -->
<input bind:value={title} type="text" />
<p>Typing: {title}</p>

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

<!-- Select box -->
<select bind:value={role}>
    <option value="viewer">Viewer</option>
    <option value="editor">Editor</option>
    <option value="admin">Admin</option>
</select>
```

Equivalent to Vue's `v-model`. React requires you to hand-wire `onChange` handlers, but Svelte lets you write this declaratively with `bind:`.

***

## Page component basics

Inertia page components are ordinary Svelte 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),
        ]);
    }
}
```

### Svelte page component

In Svelte 5, use the `$props()` rune to receive props.

```svelte theme={null}
<!-- resources/js/pages/posts/Index.svelte -->
<script lang="ts">
    import { Link } from '@inertiajs/svelte'

    type Post = {
        id: number
        title: string
        created_at: string
    }

    type Props = {
        posts: {
            data: Post[]
        }
    }

    let { posts }: Props = $props()
</script>

<div>
    <h1>Posts</h1>
    {#each posts.data as post (post.id)}
        <article>
            <h2>
                <Link href={`/posts/${post.id}`}>{post.title}</Link>
            </h2>
            <p>{post.created_at}</p>
        </article>
    {/each}
</div>
```

Just receive props with `$props()` and use them in the template. No REST API definition needed.

***

## The `Link` component

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

```svelte theme={null}
<script lang="ts">
    import { Link } from '@inertiajs/svelte'
</script>

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

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

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

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

***

## The `Form` component

The `<Form>` component from `@inertiajs/svelte` is the recommended form submission style used in the starter kit auth screens. Specify `action` and `method` as props and write the interior with a **`{#snippet}`**.

### Basic usage

```svelte theme={null}
<script lang="ts">
    import { Form } from '@inertiajs/svelte'
</script>

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

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

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

`{#snippet children({ errors, processing })}` is Svelte's snippet syntax—a content block passed to a component (equivalent to Vue slots or React render props). `errors` and `processing` are computed and provided automatically by the `Form` component. Form fields use HTML-native `name` attributes rather than `bind:value`, 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>`.

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

<Form
    {...store.form()}
    resetOnSuccess={['password']}
    class="flex flex-col gap-6"
>
    {#snippet children({ errors, processing })}
        <!-- Form contents -->
    {/snippet}
</Form>
```

Fields listed in `resetOnSuccess` 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` hook

For form handling, use `@inertiajs/svelte`'s `useForm` hook. 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.');
    }
}
```

### Svelte form component

```svelte theme={null}
<!-- resources/js/pages/posts/Create.svelte -->
<script lang="ts">
    import { useForm } from '@inertiajs/svelte'

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

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/posts')
    }
</script>

<form onsubmit={submit}>
    <div>
        <label>Title</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>Content</label>
        <textarea bind:value={form.data.content}></textarea>
        {#if form.errors.content}
            <p class="error">{form.errors.content}</p>
        {/if}
    </div>

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

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 `bind:value`, 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 Svelte component.

```svelte theme={null}
<script lang="ts">
    import { usePage } from '@inertiajs/svelte'

    type SharedProps = {
        auth: {
            user: { id: number; name: string; email: string } | null
        }
        flash: {
            success: string | null
            error: string | null
        }
    }

    const page = usePage<SharedProps>()
</script>

<header>
    {#if page.props.auth.user}
        <span>{page.props.auth.user.name}</span>
    {:else}
        <span>Guest</span>
    {/if}
</header>

{#if page.props.flash.success}
    <div class="alert-success">{page.props.flash.success}</div>
{/if}
```

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

***

## Svelte 5 reactivity (runes)

Here are the Svelte 5 runes to know when developing with Inertia × Svelte.

### `$state` — reactive state

```svelte theme={null}
<script lang="ts">
    let count = $state(0)
    let isOpen = $state(false)
    let items = $state<string[]>([])
</script>

<p>{count}</p>
<button onclick={() => count++}>+1</button>
<button onclick={() => isOpen = !isOpen}>Toggle</button>
```

Variables declared with `$state` become automatically reactive. When the value changes, the DOM updates automatically. It corresponds to Vue's `ref` or React's `useState`, but you don't need `.value`—just assign to update state.

### `$derived` — derived values

```svelte theme={null}
<script lang="ts">
    let posts = $state<{ title: string; published: boolean }[]>([])

    // Extract only published posts
    let publishedPosts = $derived(posts.filter(post => post.published))

    // With multiple dependencies
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `${published} of ${total} posts published`
    })
</script>

<p>{summary}</p>
```

`$derived` automatically recomputes when its dependencies change. Corresponds to Vue's `computed` or React's `useMemo`.

### `$effect` — side effects

```svelte theme={null}
<script lang="ts">
    let query = $state('')

    // Runs whenever query changes
    $effect(() => {
        console.log('Query changed:', query)

        // You can return a cleanup function
        return () => {
            console.log('Cleanup')
        }
    })
</script>

<input bind:value={query} placeholder="Search..." />
```

`$effect` runs each time a `$state` dependency changes. It corresponds to React's `useEffect`, but you don't need to specify a dependency array—the `$state` variables used are tracked automatically.

***

## shadcn-svelte components

The starter kit includes [shadcn-svelte](https://www.shadcn-svelte.com/). shadcn-svelte is a component library with the same design philosophy as shadcn/ui for React—copy the code into your project and freely customize.

### Adding a component

```shell theme={null}
npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add input
npx shadcn-svelte@latest add card
```

Running the command places the component source into `resources/js/components/ui/`.

### Usage

```svelte theme={null}
<script lang="ts">
    import { Button } from '@/components/ui/button'
    import { Input } from '@/components/ui/input'
    import * as Card from '@/components/ui/card'
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({ email: '', password: '' })

    function submit(e: SubmitEvent) {
        e.preventDefault()
        form.post('/login')
    }
</script>

<Card.Root class="w-96">
    <Card.Header>
        <Card.Title>Log in</Card.Title>
    </Card.Header>
    <Card.Content>
        <form onsubmit={submit} class="space-y-4">
            <Input
                type="email"
                bind:value={form.data.email}
                placeholder="Email"
            />
            {#if form.errors.email}
                <p class="text-sm text-red-500">{form.errors.email}</p>
            {/if}

            <Input
                type="password"
                bind:value={form.data.password}
                placeholder="Password"
            />

            <Button type="submit" disabled={form.processing} class="w-full">
                {form.processing ? 'Signing in...' : 'Sign in'}
            </Button>
        </form>
    </Card.Content>
</Card.Root>
```

The starter kit already includes commonly used components like Button, Input, Card, Dialog, and Dropdown. Add more as needed with `npx shadcn-svelte@latest add`.

***

## Summary

Svelte shines when paired with Laravel, especially in a "modern monolith" configuration through Inertia. The compiler-based design keeps bundle sizes small, and the runes syntax makes reactivity explicit and easy to understand.

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

Inertia × Svelte combines the simplicity of a Laravel backend with Svelte's compact style, offering a great development experience. 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 Vue.js — the essentials for using it with Inertia × Laravel](/en/blog/vue-introduction.md)
- [Getting started with Laravel testing using Pest PHP](/en/blog/pest-introduction.md)
- [Installation](/en/installation.md)
- [Getting started with Laravel Nightwatch](/en/blog/nightwatch-introduction.md)
