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

# Introduction à Svelte — bases pour l'utiliser avec Inertia × Laravel

> Guide d'introduction à Svelte avec Laravel + Inertia.js. De l'approche « compilateur » et de la syntaxe runes de Svelte 5 aux hooks Inertia (useForm, usePage, etc.).

## Qu'est-ce que Svelte ?

[Svelte](https://svelte.jp/) occupe une place à part parmi les frameworks JavaScript. Alors que React et Vue fonctionnent comme des **bibliothèques d'exécution**, Svelte est un **compilateur** : au moment du build, les composants sont transformés en pur JavaScript, ce qui évite d'envoyer un runtime de framework au navigateur.

Le point le plus caractéristique est l'**absence de Virtual DOM**. Quand l'état change, le code généré à la compilation met à jour directement le DOM. Il en résulte une UI très légère et rapide.

<Info>
  Cette page présente la combinaison Svelte 5 + Inertia v3, qui est la configuration par défaut du starter kit Laravel 13.
</Info>

### Les runes de Svelte 5

Svelte 5 (sorti en 2024) introduit un nouveau système de réactivité appelé **runes**. On déclare l'état réactif via des fonctions spéciales (`$state`, `$derived`, `$effect`, etc.). Contrairement à la réactivité implicite des versions antérieures, cette approche est explicite et plus prévisible.

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

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

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

<Tip>
  Le starter kit Svelte de Laravel utilise Svelte 5 + TypeScript par défaut. Tous les exemples de cette page sont en TypeScript (`lang="ts"`).
</Tip>

***

## Place de Svelte dans Laravel

### Historique

La relation entre Svelte et Laravel est très récente comparée à Vue et React : l'intégration officielle passe par les **starter kits**.

```mermaid theme={null}
timeline
    title Historique Laravel et Svelte
    2016 : Sortie de Svelte 1 (Rich Harris)
    2019 : Svelte 3 — refonte basée sur un compilateur
    2021 : Inertia.js — adaptateur Svelte communautaire
    2024 : Svelte 5 — introduction des runes
    2026 : Laravel 13 — starter kit Svelte officiellement ajouté (compatible Inertia v3)
```

En **2026, avec Laravel 13**, Svelte devient officiellement l'une des options frontend de l'écosystème Laravel. Il figure au même titre que Vue et React dans le prompt interactif de `laravel new`.

Ce framework reste peu connu des utilisateurs Laravel, mais il se distingue par **une taille de bundle réduite grâce au compilateur** et **une syntaxe simple** : en venant de Vue ou React, la quantité de code à écrire surprend souvent par sa concision.

### Style dominant actuel : Inertia × Svelte

L'usage principal de Svelte avec Laravel est **Inertia × Svelte**. Inertia offre une architecture « monolithe moderne » où les données du contrôleur Laravel sont directement passées au composant Svelte, sans avoir à concevoir une API.

```mermaid theme={null}
graph LR
    Browser["Navigateur"]
    Inertia["Inertia.js<br>(adaptateur)"]
    Laravel["Laravel<br>(contrôleur)"]
    Svelte["Svelte<br>(composant de page)"]

    Browser <-->|XHR / rechargement complet| Inertia
    Inertia <-->|Réponse Inertia| Laravel
    Inertia -->|props| Svelte
    Svelte -->|rendu| Browser
```

***

## Mise en place

### Via un starter kit (recommandé)

Le plus simple pour un nouveau projet.

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

En choisissant **Svelte** dans le prompt, tout est configuré automatiquement :

* `inertiajs/inertia-laravel` (adaptateur serveur)
* `@inertiajs/svelte` (adaptateur client)
* `svelte` + `@sveltejs/vite-plugin-svelte` (Svelte 5 et plugin Vite)
* TypeScript + `svelte-check`
* Tailwind CSS + shadcn-svelte
* Middleware `HandleInertiaRequests`
* Écrans d'authentification (déjà implémentés en Inertia + Svelte + TypeScript)

### Installation manuelle

Pour un projet existant, installez séparément côté serveur et côté client.

```shell theme={null}
# Côté serveur (PHP)
composer require inertiajs/inertia-laravel

# Côté client (JavaScript)
npm install @inertiajs/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
```

Ajoutez le plugin Svelte et le plugin Vite d'Inertia à `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(),
    ],
})
```

Démarrez l'application Inertia dans `resources/js/app.ts`. Le plugin `@inertiajs/vite` prenant en charge la résolution automatique des pages et le montage, l'entrée peut se limiter au strict minimum.

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

createInertiaApp()
```

<Info>
  Pour les détails (template racine, enregistrement du middleware, etc.), consultez la [documentation officielle Inertia](https://inertiajs.com/installation).
</Info>

***

## Structure des répertoires

Dans le starter kit, les composants de page Svelte sont placés dans `resources/js/pages/`.

```
resources/js/
├── app.ts             # Point d'entrée de l'application Inertia
├── components/        # Composants UI réutilisables
│   └── ui/            # Composants shadcn-svelte
├── layouts/           # Composants de layout
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # Utilitaires et modules runes Svelte
├── pages/             # Composants de page Inertia (correspondent au contrôleur)
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # Définitions de types TypeScript
```

`Inertia::render('posts/Index', [...])` correspond au composant `resources/js/pages/posts/Index.svelte`.

***

## Structure d'un fichier Svelte

Un fichier `.svelte` se compose de trois blocs : **`<script>`, template et `<style>`**.

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

<!-- Template (syntaxe HTML) -->
<h1>Bonjour, {name} !</h1>

<style>
    /* CSS scopé (limité au composant) */
    h1 {
        color: #ff2d20;
    }
</style>
```

C'est proche des SFC de Vue, mais avec encore moins de code à écrire. Le `<style>` étant scopé par défaut, on ne se soucie plus des collisions de noms de classes.

### Syntaxe du template

#### Interpolation et expressions

Dans le template, `{}` intègre une valeur ou une expression JavaScript.

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

<p>Bonjour, {name} !</p>
<p>Le double : {count * 2}</p>
```

#### `{#if}` — branchement

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

{#if isLoggedIn}
    <p>Bienvenue !</p>
{:else if role === 'admin'}
    <p>Connecté en tant qu'administrateur</p>
{:else}
    <a href="/login">Connexion</a>
{/if}
```

Équivalent au `v-if` / `v-else` de Vue et au ternaire de React.

#### `{#each}` — rendu de liste

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

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

`(post.id)` sert de clé et permet un diff efficace. Équivalent au `v-for` de Vue et au `Array.map()` de React.

#### `bind:` — binding bidirectionnel

`bind:value` synchronise la valeur d'un élément de formulaire avec une variable réactive dans les deux sens.

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

<!-- Champ texte -->
<input bind:value={title} type="text" />
<p>Saisie : {title}</p>

<!-- Case à cocher -->
<input bind:checked={agreed} type="checkbox" />
<p>Accord : {agreed}</p>

<!-- Liste déroulante -->
<select bind:value={role}>
    <option value="viewer">Lecteur</option>
    <option value="editor">Éditeur</option>
    <option value="admin">Administrateur</option>
</select>
```

Équivalent au `v-model` de Vue. En React, il fallait écrire un `onChange` ; Svelte l'exprime de manière déclarative avec `bind:`.

***

## Composant de page — bases

Un composant de page Inertia est un composant Svelte classique. Les données passées par le contrôleur arrivent en props.

### Contrôleur

```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),
        ]);
    }
}
```

### Composant de page Svelte

En Svelte 5, on reçoit les props avec la rune `$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>Liste des articles</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>
```

Il suffit de récupérer les props via `$props()` pour utiliser les données transmises par le contrôleur. Aucune API REST à définir.

***

## Composant `Link`

Le composant `<Link>` de `@inertiajs/svelte` déclenche la navigation en XHR sans rechargement complet.

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

<!-- Lien de base -->
<Link href="/posts">Liste des articles</Link>

<!-- Lien POST (suppression, etc.) -->
<Link href="/posts/1" method="delete" as="button">
    Supprimer
</Link>

<!-- Préchargement au survol -->
<Link href="/posts/1" preload>Voir l'article</Link>
```

L'écriture est identique à celle d'une balise `<a>`, mais seul le composant de page est remplacé en coulisses, offrant une expérience SPA.

***

## Composant `Form`

Le composant `<Form>` de `@inertiajs/svelte` est l'écriture recommandée dans les écrans d'authentification du starter kit. On passe `action` et `method` en props, et le contenu se décrit avec **`{#snippet}`**.

### Utilisation de base

```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">Titre</label>
            <input id="title" name="title" type="text" required />
            {#if errors.title}
                <p class="error">{errors.title}</p>
            {/if}
        </div>

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

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

`{#snippet children({ errors, processing })}` est la syntaxe de snippet Svelte — un bloc de contenu passé au composant (équivalent des slots de Vue ou des render props de React). Les valeurs `errors` et `processing` sont calculées et fournies par le composant `Form`. Les champs utilisent l'attribut natif `name` plutôt que `bind:value`, ce qui active la collecte standard des données de formulaire par le navigateur.

### Patron du starter kit

Le starter kit utilise [Wayfinder](/fr/blog/wayfinder-introduction) pour gérer les routes en tant qu'objets. `store.form()` renvoie un objet contenant `action` et `method` que l'on spread dans `<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 })}
        <!-- Contenu du formulaire -->
    {/snippet}
</Form>
```

Les champs listés dans `resetOnSuccess` sont réinitialisés automatiquement après un envoi réussi. Utile pour les champs de mot de passe à vider.

<Info>
  Sans Wayfinder, `action="/login"` fonctionne de la même façon.
</Info>

***

## Hook `useForm`

Pour la gestion de formulaires, utilisez le hook `useForm` de `@inertiajs/svelte`.

### Côté contrôleur

```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', 'Article créé.');
    }
}
```

### Composant de formulaire Svelte

```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>Titre</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>Contenu</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 ? 'Envoi...' : 'Publier'}
    </button>
</form>
```

Principales propriétés de l'objet retourné par `useForm`.

| Propriété / Méthode | Description                                       |
| ------------------- | ------------------------------------------------- |
| `form.data`         | Objet des données du formulaire                   |
| `form.errors`       | Erreurs de validation (par nom de champ)          |
| `form.processing`   | `true` pendant l'envoi                            |
| `form.isDirty`      | `true` si la valeur diffère de l'initiale         |
| `form.post(url)`    | Envoi POST                                        |
| `form.put(url)`     | Envoi PUT (mise à jour)                           |
| `form.delete(url)`  | Envoi DELETE                                      |
| `form.reset()`      | Réinitialiser le formulaire aux valeurs initiales |

En cas d'erreur de validation, `useForm` conserve les valeurs saisies. Combiné à `bind:value`, l'expérience de formulaire est fluide.

***

## Données partagées (Shared Data)

Les données communes à toutes les pages (utilisateur connecté, messages flash) sont déclarées dans la méthode `share()` du middleware `HandleInertiaRequests`.

```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'),
            ],
        ]);
    }
}
```

Depuis un composant Svelte, on accède à ces données partagées via `usePage()`.

```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>Invité</span>
    {/if}
</header>

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

<Info>
  Ces données étant présentes à chaque requête, limitez-les au strict nécessaire. Utilisez une évaluation paresseuse avec `fn()` pour qu'elles ne soient évaluées qu'en cas d'accès.
</Info>

***

## Réactivité de Svelte 5 (runes)

Les runes essentielles pour développer avec Inertia × Svelte.

### `$state` — état réactif

```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}>Basculer</button>
```

Une variable déclarée avec `$state` est automatiquement réactive : lorsque sa valeur change, le DOM est mis à jour. C'est l'équivalent de `ref` en Vue ou `useState` en React, mais sans `.value` : il suffit d'une affectation pour mettre à jour.

### `$derived` — valeur dérivée

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

    // On extrait uniquement les articles publiés
    let publishedPosts = $derived(posts.filter(post => post.published))

    // Plusieurs dépendances
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `${published} publié(s) sur ${total} au total`
    })
</script>

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

`$derived` recalcule automatiquement lorsque ses dépendances changent. Équivalent à `computed` en Vue ou `useMemo` en React.

### `$effect` — effets de bord

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

    // Exécuté à chaque changement de query
    $effect(() => {
        console.log('Nouvelle requête :', query)

        // Une fonction de nettoyage peut être retournée
        return () => {
            console.log('Nettoyage')
        }
    })
</script>

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

`$effect` se déclenche à chaque variation d'un `$state` utilisé. Équivalent à `useEffect` en React, mais sans tableau de dépendances : les variables `$state` utilisées sont détectées automatiquement.

***

## Composants shadcn-svelte

Le starter kit intègre [shadcn-svelte](https://www.shadcn-svelte.com/). Cette bibliothèque a le même esprit que shadcn/ui pour React : vous copiez le code dans le projet et le personnalisez librement.

### Ajouter des composants

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

Ces commandes déposent le code du composant dans `resources/js/components/ui/`.

### Utilisation

```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>Connexion</Card.Title>
    </Card.Header>
    <Card.Content>
        <form onsubmit={submit} class="space-y-4">
            <Input
                type="email"
                bind:value={form.data.email}
                placeholder="Adresse e-mail"
            />
            {#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="Mot de passe"
            />

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

Le starter kit inclut déjà Button, Input, Card, Dialog, Dropdown, etc. Ajoutez d'autres composants au besoin avec `npx shadcn-svelte@latest add`.

***

## Conclusion

Svelte s'exprime particulièrement bien avec Laravel dans un « monolithe moderne » via Inertia. Son bundle réduit grâce au compilateur et la clarté des runes en font un choix pertinent.

| Élément                  | Rôle                                                   |
| ------------------------ | ------------------------------------------------------ |
| Contrôleur Laravel       | Routage, récupération de données, validation           |
| `Inertia::render()`      | Transmet les données du contrôleur au composant Svelte |
| Composant de page Svelte | Reçoit les props via `$props()` et rend l'UI           |
| `useForm`                | Gestion d'état, envoi et erreurs de formulaire         |
| Composant `Link`         | Navigation sans rechargement                           |
| `usePage().props`        | Accès aux données partagées                            |
| shadcn-svelte            | Bibliothèque de composants standard                    |

Inertia × Svelte combine la simplicité du backend Laravel à l'écriture compacte de Svelte. Le starter kit permet de démarrer immédiatement, écrans d'authentification inclus.

<Card title="Documentation officielle Inertia.js" icon="book-open" href="https://inertiajs.com">
  Consultez la documentation officielle pour toutes les fonctionnalités d'Inertia v3.
</Card>


## Related topics

- [Introduction à React — bases pour l'utiliser avec Inertia × Laravel](/fr/blog/react-introduction.md)
- [Introduction à Vue.js — les bases pour l'utiliser avec Inertia × Laravel](/fr/blog/vue-introduction.md)
- [Frontend](/fr/frontend.md)
- [Introduction à Eloquent](/fr/eloquent.md)
- [Introduction aux tests](/fr/testing.md)
