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

# Vue to Svelte Migration Guide

> Comprehensive guide for migrating from Vue to Svelte, including concept mapping, syntax comparison, and migration strategies

Migrating from Vue to Svelte offers benefits like smaller bundle sizes, simpler reactivity, and less framework overhead. This guide will help you understand the differences and successfully migrate your Vue applications to Svelte.

## Why Migrate to Svelte?

* **True Compilation**: Svelte compiles to vanilla JavaScript, no runtime framework
* **Simpler Reactivity**: No need for `.value` or reactivity APIs
* **Smaller Bundles**: Significantly smaller production builds
* **Less Magic**: More explicit, easier to understand
* **Better Performance**: Faster runtime with surgical DOM updates

## Key Concept Mapping

| Vue Concept     | Svelte Equivalent | Notes                     |
| --------------- | ----------------- | ------------------------- |
| `ref()`         | `$state`          | Similar reactive state    |
| `reactive()`    | `$state`          | Unified state primitive   |
| `computed()`    | `$derived`        | Automatic reactivity      |
| `watch()`       | `$effect`         | Side effects              |
| `watchEffect()` | `$effect`         | Auto-tracking effects     |
| `props`         | `$props()`        | Destructured props        |
| `emit()`        | Callback props    | No event emitter needed   |
| `slots`         | Snippets          | More powerful composition |
| `v-model`       | `bind:value`      | Two-way binding           |
| `v-if`          | `{#if}`           | Conditional rendering     |
| `v-for`         | `{#each}`         | List rendering            |
| `v-on`          | Event attributes  | Simpler syntax            |
| Composables     | Runes             | More powerful primitives  |

## Component Basics

### Component Definition

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div>
        <h1>Hello, {{ name }}!</h1>
      </div>
    </template>

    <script setup>
    const props = defineProps({
      name: String
    });
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let { name } = $props();
    </script>

    <div>
      <h1>Hello, {name}!</h1>
    </div>
    ```
  </Tab>
</Tabs>

### Reactive State

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <button @click="count++">
        Count: {{ count }}
      </button>
    </template>

    <script setup>
    import { ref } from 'vue';

    const count = ref(0);
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let count = $state(0);
    </script>

    <button onclick={() => count++}>
      Count: {count}
    </button>
    ```
  </Tab>
</Tabs>

### Computed Properties

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div>Total: ${{ total }}</div>
    </template>

    <script setup>
    import { ref, computed } from 'vue';

    const props = defineProps({
      items: Array
    });

    const total = computed(() => {
      return props.items.reduce((sum, item) => sum + item.price, 0);
    });
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let { items } = $props();
      
      let total = $derived(
        items.reduce((sum, item) => sum + item.price, 0)
      );
    </script>

    <div>Total: ${total}</div>
    ```
  </Tab>
</Tabs>

## Reactivity Patterns

### Watchers and Effects

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <script setup>
    import { ref, watch, watchEffect } from 'vue';

    const count = ref(0);

    // Watch specific value
    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`);
    });

    // Watch effect (auto-tracking)
    watchEffect(() => {
      console.log(`Count is ${count.value}`);
    });
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let count = $state(0);
      
      // Auto-tracking effect
      $effect(() => {
        console.log(`Count is ${count}`);
      });
      
      // Can access previous value with $effect.pre
      $effect.pre(() => {
        const previous = count;
        return () => {
          console.log(`Count changed from ${previous} to ${count}`);
        };
      });
    </script>
    ```
  </Tab>
</Tabs>

### Reactive Objects

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div>
        <p>{{ user.name }} - {{ user.email }}</p>
        <button @click="updateUser">Update</button>
      </div>
    </template>

    <script setup>
    import { reactive } from 'vue';

    const user = reactive({
      name: 'Alice',
      email: 'alice@example.com'
    });

    function updateUser() {
      user.name = 'Bob';
    }
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let user = $state({
        name: 'Alice',
        email: 'alice@example.com'
      });
      
      function updateUser() {
        user.name = 'Bob'; // Direct mutation works!
      }
    </script>

    <div>
      <p>{user.name} - {user.email}</p>
      <button onclick={updateUser}>Update</button>
    </div>
    ```
  </Tab>
</Tabs>

### Arrays and Lists

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div>
        <button @click="addItem">Add Item</button>
        <ul>
          <li v-for="item in items" :key="item.id">
            {{ item.text }}
          </li>
        </ul>
      </div>
    </template>

    <script setup>
    import { ref } from 'vue';

    const items = ref([]);

    function addItem() {
      items.value.push({ id: Date.now(), text: 'New item' });
    }
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let items = $state([]);
      
      function addItem() {
        items.push({ id: Date.now(), text: 'New item' });
      }
    </script>

    <div>
      <button onclick={addItem}>Add Item</button>
      <ul>
        {#each items as item (item.id)}
          <li>{item.text}</li>
        {/each}
      </ul>
    </div>
    ```
  </Tab>
</Tabs>

## Props and Events

### Props Definition

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div>
        <h2>{{ title }}</h2>
        <p>Age: {{ age }}</p>
      </div>
    </template>

    <script setup>
    const props = defineProps({
      title: {
        type: String,
        required: true
      },
      age: {
        type: Number,
        default: 0
      }
    });
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let { title, age = 0 } = $props();
    </script>

    <div>
      <h2>{title}</h2>
      <p>Age: {age}</p>
    </div>
    ```
  </Tab>
</Tabs>

### Event Emission

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <!-- Child.vue -->
    <template>
      <button @click="handleClick">
        Click me
      </button>
    </template>

    <script setup>
    const emit = defineEmits(['click']);

    function handleClick() {
      emit('click', { timestamp: Date.now() });
    }
    </script>

    <!-- Parent.vue -->
    <template>
      <Child @click="onChildClick" />
    </template>

    <script setup>
    function onChildClick(data) {
      console.log('Child clicked:', data);
    }
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <!-- Child.svelte -->
    <script>
      let { onclick } = $props();
      
      function handleClick() {
        onclick?.({ timestamp: Date.now() });
      }
    </script>

    <button onclick={handleClick}>
      Click me
    </button>

    <!-- Parent.svelte -->
    <script>
      function onChildClick(data) {
        console.log('Child clicked:', data);
      }
    </script>

    <Child onclick={onChildClick} />
    ```
  </Tab>
</Tabs>

### Two-Way Binding (v-model)

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <!-- Child.vue -->
    <template>
      <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
    </template>

    <script setup>
    defineProps(['modelValue']);
    defineEmits(['update:modelValue']);
    </script>

    <!-- Parent.vue -->
    <template>
      <Child v-model="text" />
      <p>{{ text }}</p>
    </template>

    <script setup>
    import { ref } from 'vue';
    const text = ref('');
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <!-- Child.svelte -->
    <script>
      let { value = $bindable() } = $props();
    </script>

    <input bind:value />

    <!-- Parent.svelte -->
    <script>
      let text = $state('');
    </script>

    <Child bind:value={text} />
    <p>{text}</p>
    ```
  </Tab>
</Tabs>

## Template Directives

### Conditional Rendering

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div v-if="isLoggedIn">
        Welcome back!
      </div>
      <div v-else-if="isLoading">
        Loading...
      </div>
      <div v-else>
        Please log in
      </div>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    {#if isLoggedIn}
      <div>Welcome back!</div>
    {:else if isLoading}
      <div>Loading...</div>
    {:else}
      <div>Please log in</div>
    {/if}
    ```
  </Tab>
</Tabs>

### List Rendering

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <ul>
        <li v-for="(item, index) in items" :key="item.id">
          {{ index }}: {{ item.name }}
        </li>
      </ul>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <ul>
      {#each items as item, index (item.id)}
        <li>{index}: {item.name}</li>
      {/each}
    </ul>
    ```
  </Tab>
</Tabs>

### Class and Style Binding

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <!-- Class binding -->
      <div :class="{ active: isActive, 'text-danger': hasError }">
        Content
      </div>
      
      <!-- Style binding -->
      <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
        Styled
      </div>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <!-- Class binding -->
    <div class:active={isActive} class:text-danger={hasError}>
      Content
    </div>

    <!-- Style binding -->
    <div style:color={textColor} style:font-size="{fontSize}px">
      Styled
    </div>
    ```
  </Tab>
</Tabs>

### Event Handling

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <button @click="count++">{{ count }}</button>
      <button @click="handleClick($event)">Click</button>
      <form @submit.prevent="handleSubmit">
        <input v-model="text" @keyup.enter="search" />
      </form>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <button onclick={() => count++}>{count}</button>
    <button onclick={handleClick}>Click</button>
    <form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
      <input bind:value={text} onkeyup={(e) => e.key === 'Enter' && search()} />
    </form>
    ```
  </Tab>
</Tabs>

## Lifecycle Hooks

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <script setup>
    import { onMounted, onUnmounted, onBeforeUpdate, onUpdated } from 'vue';

    onMounted(() => {
      console.log('Component mounted');
    });

    onUnmounted(() => {
      console.log('Component unmounted');
    });

    onBeforeUpdate(() => {
      console.log('Before update');
    });

    onUpdated(() => {
      console.log('After update');
    });
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      import { onMount } from 'svelte';
      
      onMount(() => {
        console.log('Component mounted');
        
        return () => {
          console.log('Component unmounted');
        };
      });
      
      // Use $effect for update tracking
      $effect(() => {
        console.log('Effect runs on mount and updates');
      });
    </script>
    ```
  </Tab>
</Tabs>

## Slots and Content Projection

### Default Slot

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <!-- Card.vue -->
    <template>
      <div class="card">
        <slot></slot>
      </div>
    </template>

    <!-- Usage -->
    <template>
      <Card>
        <p>Card content</p>
      </Card>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <!-- Card.svelte -->
    <script>
      let { children } = $props();
    </script>

    <div class="card">
      {@render children()}
    </div>

    <!-- Usage -->
    <Card>
      <p>Card content</p>
    </Card>
    ```
  </Tab>
</Tabs>

### Named Slots

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <!-- Modal.vue -->
    <template>
      <div class="modal">
        <div class="header">
          <slot name="header"></slot>
        </div>
        <div class="body">
          <slot></slot>
        </div>
        <div class="footer">
          <slot name="footer"></slot>
        </div>
      </div>
    </template>

    <!-- Usage -->
    <template>
      <Modal>
        <template #header>
          <h2>Title</h2>
        </template>
        
        <p>Content</p>
        
        <template #footer>
          <button>Close</button>
        </template>
      </Modal>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <!-- Modal.svelte -->
    <script>
      let { header, children, footer } = $props();
    </script>

    <div class="modal">
      <div class="header">
        {@render header()}
      </div>
      <div class="body">
        {@render children()}
      </div>
      <div class="footer">
        {@render footer()}
      </div>
    </div>

    <!-- Usage -->
    <Modal>
      {#snippet header()}
        <h2>Title</h2>
      {/snippet}
      
      <p>Content</p>
      
      {#snippet footer()}
        <button>Close</button>
      {/snippet}
    </Modal>
    ```
  </Tab>
</Tabs>

### Scoped Slots

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <!-- List.vue -->
    <template>
      <ul>
        <li v-for="item in items" :key="item.id">
          <slot :item="item" :index="items.indexOf(item)"></slot>
        </li>
      </ul>
    </template>

    <!-- Usage -->
    <template>
      <List :items="users">
        <template #default="{ item, index }">
          <strong>{{ index }}: {{ item.name }}</strong>
        </template>
      </List>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <!-- List.svelte -->
    <script>
      let { items, children } = $props();
    </script>

    <ul>
      {#each items as item, index (item.id)}
        <li>
          {@render children(item, index)}
        </li>
      {/each}
    </ul>

    <!-- Usage -->
    <List items={users}>
      {#snippet children(item, index)}
        <strong>{index}: {item.name}</strong>
      {/snippet}
    </List>
    ```
  </Tab>
</Tabs>

## Forms and Input Binding

### Form Handling

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <form @submit.prevent="handleSubmit">
        <input v-model="formData.name" />
        <input v-model="formData.email" type="email" />
        <textarea v-model="formData.message"></textarea>
        <input v-model="formData.agree" type="checkbox" />
        <button type="submit">Submit</button>
      </form>
    </template>

    <script setup>
    import { reactive } from 'vue';

    const formData = reactive({
      name: '',
      email: '',
      message: '',
      agree: false
    });

    function handleSubmit() {
      console.log(formData);
    }
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let formData = $state({
        name: '',
        email: '',
        message: '',
        agree: false
      });
      
      function handleSubmit(e) {
        e.preventDefault();
        console.log(formData);
      }
    </script>

    <form onsubmit={handleSubmit}>
      <input bind:value={formData.name} />
      <input bind:value={formData.email} type="email" />
      <textarea bind:value={formData.message}></textarea>
      <input bind:checked={formData.agree} type="checkbox" />
      <button type="submit">Submit</button>
    </form>
    ```
  </Tab>
</Tabs>

## Provide/Inject vs Context

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <!-- Parent.vue -->
    <script setup>
    import { provide, ref } from 'vue';

    const theme = ref('dark');
    provide('theme', theme);
    </script>

    <!-- Child.vue -->
    <script setup>
    import { inject } from 'vue';

    const theme = inject('theme');
    </script>

    <template>
      <div>Theme: {{ theme }}</div>
    </template>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <!-- Parent.svelte -->
    <script>
      import { setContext } from 'svelte';
      
      let theme = $state('dark');
      setContext('theme', theme);
    </script>

    <!-- Child.svelte -->
    <script>
      import { getContext } from 'svelte';
      
      const theme = getContext('theme');
    </script>

    <div>Theme: {theme}</div>
    ```
  </Tab>
</Tabs>

## Composables vs Runes

<Tabs items={['Vue 3 Composable', 'Svelte Runes']}>
  <Tab value="Vue 3 Composable">
    ```js theme={null}
    // useCounter.js
    import { ref, computed } from 'vue';

    export function useCounter(initialValue = 0) {
      const count = ref(initialValue);
      const doubled = computed(() => count.value * 2);
      
      function increment() {
        count.value++;
      }
      
      function decrement() {
        count.value--;
      }
      
      return {
        count,
        doubled,
        increment,
        decrement
      };
    }

    // Usage
    <script setup>
    const { count, doubled, increment } = useCounter(10);
    </script>
    ```
  </Tab>

  <Tab value="Svelte Runes">
    ```js theme={null}
    // counter.svelte.js
    export function createCounter(initialValue = 0) {
      let count = $state(initialValue);
      let doubled = $derived(count * 2);
      
      function increment() {
        count++;
      }
      
      function decrement() {
        count--;
      }
      
      return {
        get count() { return count; },
        get doubled() { return doubled; },
        increment,
        decrement
      };
    }

    // Usage
    <script>
      import { createCounter } from './counter.svelte.js';
      const counter = createCounter(10);
    </script>
    ```
  </Tab>
</Tabs>

## Styling

### Scoped Styles

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div class="container">
        <button class="btn">Click me</button>
      </div>
    </template>

    <style scoped>
    .container {
      padding: 20px;
    }

    .btn {
      background: blue;
      color: white;
    }
    </style>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <div class="container">
      <button class="btn">Click me</button>
    </div>

    <style>
    /* Scoped by default - no need for 'scoped' attribute */
    .container {
      padding: 20px;
    }

    .btn {
      background: blue;
      color: white;
    }
    </style>
    ```
  </Tab>
</Tabs>

### Dynamic Styles

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <div :style="{ color: textColor, fontSize: size + 'px' }">
        Styled text
      </div>
    </template>

    <script setup>
    import { ref } from 'vue';

    const textColor = ref('red');
    const size = ref(16);
    </script>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      let textColor = $state('red');
      let size = $state(16);
    </script>

    <div style:color={textColor} style:font-size="{size}px">
      Styled text
    </div>
    ```
  </Tab>
</Tabs>

## Transitions and Animations

<Tabs items={['Vue 3', 'Svelte']}>
  <Tab value="Vue 3">
    ```vue theme={null}
    <template>
      <Transition name="fade">
        <div v-if="show">Content</div>
      </Transition>
    </template>

    <style>
    .fade-enter-active,
    .fade-leave-active {
      transition: opacity 0.5s;
    }

    .fade-enter-from,
    .fade-leave-to {
      opacity: 0;
    }
    </style>
    ```
  </Tab>

  <Tab value="Svelte">
    ```svelte theme={null}
    <script>
      import { fade } from 'svelte/transition';
      let show = $state(true);
    </script>

    {#if show}
      <div transition:fade={{ duration: 500 }}>
        Content
      </div>
    {/if}
    ```
  </Tab>
</Tabs>

## Migration Checklist

* [ ] Understand Svelte's compilation model
* [ ] Convert Vue components to Svelte components
* [ ] Replace `ref()` and `reactive()` with `$state`
* [ ] Replace `computed()` with `$derived`
* [ ] Replace `watch()` and `watchEffect()` with `$effect`
* [ ] Convert `defineProps()` to `$props()`
* [ ] Replace `defineEmits()` with callback props
* [ ] Update template syntax (`v-if` → `{#if}`, etc.)
* [ ] Convert `v-model` to `bind:value`
* [ ] Replace slots with snippets
* [ ] Update event handlers (`@click` → `onclick`)
* [ ] Convert provide/inject to context API
* [ ] Update composables to use runes
* [ ] Migrate scoped styles (already default in Svelte)
* [ ] Update transitions and animations
* [ ] Test all functionality thoroughly

## Key Differences to Remember

1. **No `.value`**: In Svelte, state is accessed directly without `.value`
2. **Scoped by Default**: Styles are scoped without a `scoped` attribute
3. **No Setup Function**: No need for `<script setup>` - it's the default
4. **True Compilation**: Svelte compiles to vanilla JavaScript
5. **Simpler Syntax**: Less boilerplate, more intuitive
6. **Built-in Animations**: Transitions and animations are built-in

## Next Steps

* Read the [Svelte Tutorial](https://svelte.dev/tutorial)
* Explore [Svelte Examples](https://svelte.dev/examples)
* Check out [SvelteKit](https://kit.svelte.dev) for full-stack applications
* Join the [Svelte Discord](https://svelte.dev/chat)
