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

# Introducción a Svelte — Fundamentos para usarlo con Inertia × Laravel

> Guía de introducción para usar Svelte con Laravel + Inertia.js. Se explica de forma práctica desde el enfoque basado en compilador y la sintaxis de runes de Svelte 5 hasta los hooks Inertia Svelte: useForm, usePage, etc.

## ¿Qué es Svelte?

[Svelte](https://svelte.jp/) ocupa una posición única entre los frameworks JavaScript. Mientras React o Vue funcionan como **bibliotecas de runtime**, Svelte actúa como **compilador**. Como convierte los componentes en JavaScript puro en tiempo de build, no hace falta enviar al navegador código extra del framework.

La mayor característica de Svelte es que **no usa Virtual DOM**. Cuando cambia el estado, el código generado por Svelte en tiempo de compilación actualiza directamente el DOM. Con ello se consigue una UI muy ligera y rápida.

<Info>
  En esta página se explica la combinación de Svelte 5 e Inertia v3. Los starter kits de Laravel 13 usan esta configuración por defecto.
</Info>

### Runes de Svelte 5

En Svelte 5 (lanzado en 2024) se introdujo un nuevo sistema de reactividad llamado **runes**. Se declaran estados reactivos usando funciones especiales (runes) como `$state`, `$derived` o `$effect`. A diferencia de la reactividad implícita anterior a Svelte 4, el diseño es explícito y más predecible.

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

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

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

<Tip>
  El starter kit de Svelte de Laravel usa Svelte 5 + TypeScript como estándar. Todos los ejemplos de esta página también están en TypeScript (`lang="ts"`).
</Tip>

***

## Posición dentro de Laravel

### Historia

La relación entre Svelte y Laravel es muy reciente en comparación con Vue y React; el primer soporte oficial es su adopción en los **starter kits oficiales**.

```mermaid theme={null}
timeline
    title Recorrido de Laravel y Svelte
    2016 : Lanzamiento de Svelte 1 (por Rich Harris)
    2019 : Svelte 3 — Rediseño con base en compilador
    2021 : Inertia.js — La comunidad empieza a ofrecer adaptador para Svelte
    2024 : Svelte 5 — Introducción de la sintaxis de runes
    2026 : Laravel 13 — Adición oficial del starter kit de Svelte (compatible con Inertia v3)
```

Con la adición oficial de Svelte a los starter kits en **Laravel 13 (2026)**, Svelte pasó a ser una de las opciones oficiales de frontend en el ecosistema Laravel. Se trata en igualdad de condiciones que Vue y React, y se puede seleccionar en el prompt interactivo de `laravel new`.

Es un framework aún poco conocido entre los usuarios Laravel, pero sus características son **bundles ligeros gracias al compilador** y **sintaxis sencilla**; al pasar desde Vue o React, puede sorprender la poca cantidad de código a escribir.

### Estilo dominante actual: Inertia × Svelte

El uso central de Svelte en Laravel actualmente es **Inertia × Svelte**. Inertia permite realizar una arquitectura de "monolito moderno" en la que se pasan datos directamente desde los controladores de Laravel a componentes Svelte sin diseñar una API.

```mermaid theme={null}
graph LR
    Browser["Navegador"]
    Inertia["Inertia.js<br>(capa de adaptador)"]
    Laravel["Laravel<br>(controlador)"]
    Svelte["Svelte<br>(componente de página)"]

    Browser <-->|XHR / carga de página completa| Inertia
    Inertia <-->|respuesta Inertia| Laravel
    Inertia -->|props| Svelte
    Svelte -->|renderiza| Browser
```

***

## Configuración

### Vía starter kit (recomendado)

Al iniciar un proyecto nuevo, usar el starter kit es lo más sencillo.

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

Si eliges **Svelte** en el prompt interactivo, se configura automáticamente todo lo siguiente.

* `inertiajs/inertia-laravel` (adaptador del lado servidor)
* `@inertiajs/svelte` (adaptador del lado cliente)
* `svelte` + `@sveltejs/vite-plugin-svelte` (Svelte 5 y plugin de Vite)
* TypeScript + `svelte-check`
* Tailwind CSS + biblioteca de componentes shadcn-svelte
* Middleware `HandleInertiaRequests`
* Pantallas de autenticación como login y registro (ya implementadas con Inertia + Svelte + TypeScript)

### Instalación manual

Si vas a añadirlo a un proyecto existente, instala por separado el lado servidor y el lado cliente.

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

# Lado cliente (JavaScript)
npm install @inertiajs/svelte @inertiajs/vite svelte
npm install --save-dev @sveltejs/vite-plugin-svelte svelte-check typescript
```

A continuación, añade el plugin de Svelte y el plugin de Vite de Inertia en `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(),
    ],
})
```

Arranca la app Inertia en `resources/js/app.ts`. Como el plugin `@inertiajs/vite` resuelve y monta automáticamente las páginas, basta con un entry point mínimo.

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

createInertiaApp()
```

<Info>
  Para los detalles de la instalación manual (configuración de la plantilla raíz, registro del middleware, etc.), consulta la [documentación oficial de Inertia](https://inertiajs.com/installation).
</Info>

***

## Estructura de directorios

En los starter kits, los componentes de página de Svelte se colocan en el directorio `resources/js/pages/`.

```
resources/js/
├── app.ts             # Punto de entrada de la app Inertia
├── components/        # Componentes de UI reutilizables
│   └── ui/            # Componentes shadcn-svelte
├── layouts/           # Componentes de layout
│   ├── AppLayout.svelte
│   └── AuthLayout.svelte
├── lib/               # Utilidades y módulos de runes de Svelte
├── pages/             # Componentes de página de Inertia (corresponden al nombre del controlador)
│   ├── auth/
│   │   ├── Login.svelte
│   │   └── Register.svelte
│   ├── Dashboard.svelte
│   └── posts/
│       ├── Index.svelte
│       ├── Create.svelte
│       └── Show.svelte
└── types/             # Definiciones de tipo TypeScript
```

Cuando escribes `Inertia::render('posts/Index', [...])`, el componente correspondiente es `resources/js/pages/posts/Index.svelte`.

***

## Estructura básica de un archivo Svelte

Un archivo `.svelte` se compone de tres bloques: **`<script>`, plantilla y `<style>`**.

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

<!-- Plantilla (notación tipo HTML) -->
<h1>Hello, {name}!</h1>

<style>
    /* CSS con scope (se aplica solo a este componente) */
    h1 {
        color: #ff2d20;
    }
</style>
```

Tiene una estructura parecida al Single File Component (SFC) de Vue, pero se caracteriza por escribir menos código de plantilla y script. Como `<style>` está limitado a un scope por defecto, no hay que preocuparse por colisiones de nombres de clase.

### Sintaxis de plantilla

#### Interpolación de variables y expresiones

Dentro de la plantilla se usa `{}` para incrustar valores o expresiones de JavaScript.

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

<p>Hola, {name}!</p>
<p>El doble es {count * 2}</p>
```

#### `{#if}` — Condicionales

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

{#if isLoggedIn}
    <p>¡Bienvenido!</p>
{:else if role === 'admin'}
    <p>Sesión iniciada como administrador</p>
{:else}
    <a href="/login">Iniciar sesión</a>
{/if}
```

Equivale a `v-if` / `v-else` de Vue o al operador ternario de React.

#### `{#each}` — Renderizado de listas

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

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

`(post.id)` indica la key y se usa para diff eficiente. Equivale a `v-for` de Vue o a `Array.map()` de React.

#### `bind:` — Binding bidireccional

Con `bind:value` puedes sincronizar bidireccionalmente el valor de un elemento de formulario con una variable reactiva.

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

<!-- Entrada de texto -->
<input bind:value={title} type="text" />
<p>Escribiendo: {title}</p>

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

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

Equivale a `v-model` de Vue. En React había que escribir a mano los handlers `onChange`, pero en Svelte se hace de forma declarativa con `bind:`.

***

## Fundamentos del componente de página

Los componentes de página de Inertia son componentes Svelte normales. Los datos pasados desde el controlador de Laravel se reciben como props.

### Controlador

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

### Componente de página Svelte

En Svelte 5 se reciben las props con 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>Lista de publicaciones</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>
```

Solo con recibir las props con `$props()` puedes usar en la plantilla los datos pasados desde el controlador. No hace falta definir una API REST.

***

## Componente `Link`

Al usar el componente `<Link>` que ofrece `@inertiajs/svelte`, la navegación entre páginas se hace por XHR y se evita la recarga completa del navegador.

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

<!-- Enlace básico -->
<Link href="/posts">Lista de publicaciones</Link>

<!-- Enlace con método POST (para borrar, etc.) -->
<Link href="/posts/1" method="delete" as="button">
    Borrar
</Link>

<!-- Preload (precarga al pasar el ratón) -->
<Link href="/posts/1" preload>Ver publicación</Link>
```

Se escribe igual que una etiqueta `<a>` normal, pero por debajo Inertia solo cambia el componente de página, con lo que se logra una sensación de operación como en una SPA.

***

## Componente `Form`

El componente `<Form>` que ofrece `@inertiajs/svelte` es el estilo recomendado de envío de formularios que se usa en las pantallas de autenticación del starter kit. Se indican `action` y `method` como props, y el contenido interno se describe con **`{#snippet}`**.

### Uso básico

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

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

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

`{#snippet children({ errors, processing })}` es la sintaxis de snippet de Svelte: un bloque de contenido que se pasa al componente (equivale a los slots de Vue o los render props de React). `errors` y `processing` los calcula automáticamente el componente `Form`. En los campos del formulario, en lugar de `bind:value`, se usa el atributo nativo `name` de HTML, y funciona la recolección estándar de datos de formulario del navegador.

### Patrón del starter kit

El starter kit gestiona las rutas como objetos usando [Wayfinder](/es/blog/wayfinder-introduction). `store.form()` devuelve un objeto que incluye `action` y `method` del objeto de ruta, y se aplica al `<Form>` con spread.

```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 })}
        <!-- Contenido del formulario -->
    {/snippet}
</Form>
```

Los campos indicados en `resetOnSuccess` se resetean automáticamente al enviar con éxito. Se indican en campos que se quieren dejar vacíos tras el envío, como la contraseña.

<Info>
  Si no usas Wayfinder, pasando directamente la URL con `action="/login"` funciona igual.
</Info>

***

## Hook `useForm`

Para el procesamiento de formularios se usa el hook `useForm` de `@inertiajs/svelte`. La gestión del estado del formulario, el envío y la visualización de errores de validación se implementan de forma sencilla.

### Lado del controlador

```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', 'Se ha creado la publicación.');
    }
}
```

### Componente de formulario 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>Título</label>
        <input type="text" bind:value={form.data.title} />
        {#if form.errors.title}
            <p class="error">{form.errors.title}</p>
        {/if}
    </div>

    <div>
        <label>Contenido</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 ? 'Enviando...' : 'Publicar'}
    </button>
</form>
```

Resumen de las principales propiedades del objeto que devuelve `useForm`.

| Propiedad / Método | Descripción                                             |
| ------------------ | ------------------------------------------------------- |
| `form.data`        | Objeto de datos del formulario                          |
| `form.errors`      | Errores de validación (acceso por nombre de campo)      |
| `form.processing`  | `true` durante el envío (útil para desactivar el botón) |
| `form.isDirty`     | `true` si se ha cambiado respecto al valor inicial      |
| `form.post(url)`   | Enviar con petición POST                                |
| `form.put(url)`    | Enviar con petición PUT (actualización)                 |
| `form.delete(url)` | Enviar con petición DELETE                              |
| `form.reset()`     | Resetear el formulario a los valores iniciales          |

Cuando se devuelve un error de validación, `useForm` mantiene el contenido introducido y muestra el error. Combinado con `bind:value` se logra una experiencia de formulario sin fisuras.

***

## Datos compartidos (Shared Data)

Los datos comunes a todas las páginas (información del usuario autenticado, mensajes flash, etc.) se definen en el método `share()` del 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'),
            ],
        ]);
    }
}
```

Para acceder a los datos compartidos desde un componente Svelte se usa `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>Invitado</span>
    {/if}
</header>

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

<Info>
  Como los datos compartidos se incluyen en todas las peticiones, se recomienda limitarlos al mínimo necesario. Si los pasas mediante una evaluación diferida con `fn()`, solo se evalúan cuando realmente se acceden.
</Info>

***

## Reactividad de Svelte 5 (runes)

Presentamos la sintaxis de runes de Svelte 5 que conviene conocer para desarrollar con Inertia × Svelte.

### `$state` — Estado reactivo

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

Las variables declaradas con `$state` son reactivas automáticamente. Cuando cambia el valor, el DOM se actualiza automáticamente. Equivale a `ref` de Vue o `useState` de React, pero no hace falta acceder a la propiedad `.value`; basta con asignar para actualizar el estado.

### `$derived` — Valores derivados

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

    // Extrae solo las publicaciones con published a true
    let publishedPosts = $derived(posts.filter(post => post.published))

    // Cuando hay varias dependencias
    let summary = $derived.by(() => {
        const total = posts.length
        const published = publishedPosts.length
        return `${published} publicados de un total de ${total}`
    })
</script>

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

`$derived` se recalcula automáticamente cuando cambian los valores de los que depende. Equivale a `computed` de Vue o `useMemo` de React.

### `$effect` — Procesamiento de efectos secundarios

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

    // Se ejecuta cada vez que query cambia
    $effect(() => {
        console.log('la query de búsqueda ha cambiado:', query)

        // Se puede devolver una función de limpieza
        return () => {
            console.log('limpieza')
        }
    })
</script>

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

`$effect` se ejecuta cada vez que cambia un valor `$state` del que depende. Equivale a `useEffect` de React, pero no hay que declarar explícitamente el array de dependencias; las variables `$state` usadas se rastrean automáticamente.

***

## Componentes shadcn-svelte

El starter kit incluye [shadcn-svelte](https://www.shadcn-svelte.com/). shadcn-svelte es una biblioteca de componentes con la misma filosofía de diseño que shadcn/ui para React: copias el código en tu proyecto y lo puedes personalizar libremente.

### Añadir componentes

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

Al ejecutar el comando, el código fuente del componente se coloca en `resources/js/components/ui/`.

### Cómo usar

```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>Iniciar sesión</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="Contraseña"
            />

            <Button type="submit" disabled={form.processing} class="w-full">
                {form.processing ? 'Iniciando sesión...' : 'Iniciar sesión'}
            </Button>
        </form>
    </Card.Content>
</Card.Root>
```

El starter kit incluye ya componentes de uso frecuente como Button, Input, Card, Dialog o Dropdown. Si quieres añadir más, puedes hacerlo cuando lo necesites con el comando `npx shadcn-svelte@latest add`.

***

## Conclusión

Svelte saca su potencial al combinarse con Laravel, especialmente en la configuración de "monolito moderno" a través de Inertia. Por su diseño basado en compilador, el tamaño del bundle es pequeño, y gracias a la sintaxis de runes la reactividad es explícita y fácil de entender.

| Elemento                    | Rol                                                                 |
| --------------------------- | ------------------------------------------------------------------- |
| Controlador de Laravel      | Enrutamiento, obtención de datos y validación                       |
| `Inertia::render()`         | Pasa datos del controlador al componente Svelte                     |
| Componente de página Svelte | Recibe props con `$props()` y renderiza la UI                       |
| `useForm`                   | Gestión del estado del formulario, envío y visualización de errores |
| Componente `Link`           | Navegación entre páginas sin recarga completa                       |
| `usePage().props`           | Acceso a los datos compartidos                                      |
| shadcn-svelte               | Biblioteca de componentes estándar                                  |

Con Inertia × Svelte obtienes una experiencia de desarrollo que combina la sencillez del backend Laravel con el estilo de código compacto de Svelte. Si creas el proyecto con el starter kit, puedes empezar a desarrollar de inmediato con las pantallas de autenticación incluidas.

<Card title="Documentación oficial de Inertia.js" icon="book-open" href="https://inertiajs.com">
  Para todas las funcionalidades de Inertia v3, consulta la documentación oficial.
</Card>


## Related topics

- [Introducción a React — Fundamentos para usarlo con Inertia × Laravel](/es/blog/react-introduction.md)
- [Introducción a Vue.js — Fundamentos para usarlo con Inertia × Laravel](/es/blog/vue-introduction.md)
- [Frontend](/es/frontend.md)
- [Instalación](/es/installation.md)
- [Introducción a la autenticación](/es/authentication.md)
