Skip to main content
Svelte 5 introduces significant improvements to reactivity, component architecture, and developer experience. This guide will help you migrate your Svelte 4 application to Svelte 5.

Overview of Major Changes

Svelte 5 brings several fundamental changes:
  • Runes: New reactive primitives replace legacy reactive syntax
  • Components as functions: Components are now functions, not classes
  • Snippets: Replace slots for content composition
  • Event handlers: Use callback props instead of createEventDispatcher
  • Props: New $props() rune replaces export let

Breaking Changes

The following Svelte 4 patterns are deprecated or removed in Svelte 5:
  • $: reactive statements (use $derived and $effect)
  • export let for props (use $props())
  • createEventDispatcher (use callback props)
  • <slot> elements (use snippets)
  • on: event directive (use event attributes)
  • Components as class instances (now functions)

Migration Strategy

Incremental Migration

Svelte 5 supports both legacy mode and runes mode, allowing you to migrate incrementally:
  1. Update Svelte: Install Svelte 5
  2. Component by component: Migrate one component at a time
  3. Test thoroughly: Ensure functionality remains intact
  4. Update dependencies: Check third-party packages for Svelte 5 compatibility

Automatic Migration

Svelte provides migration tools to help automate the process. Run the migration script on your codebase to get started.

Reactivity Changes

From $: to Runes

Svelte 4’s reactive statements ($:) are replaced by $derived and $effect runes.
<script>
  let count = 0;
  let doubled;
  
  // Reactive assignment
  $: doubled = count * 2;
  
  // Reactive statement
  $: console.log(`count is ${count}`);
  
  // Reactive block
  $: {
    if (count > 10) {
      console.log('Count is high!');
    }
  }
</script>

<button on:click={() => count++}>
  {count} / {doubled}
</button>

State Management

<script>
  let items = [];
  let total = 0;
  
  $: total = items.reduce((sum, item) => sum + item.price, 0);
  
  function addItem(item) {
    items = [...items, item];
  }
</script>

Props Changes

From export let to $props()

export let is deprecated in runes mode. Use the $props() rune instead.
<!-- Child.svelte -->
<script>
  export let name;
  export let age = 0;
  export let email = undefined;
</script>

<div>
  <p>{name} ({age})</p>
  {#if email}
    <p>{email}</p>
  {/if}
</div>

Bindable Props

<!-- Counter.svelte -->
<script>
  export let count = 0;
</script>

<button on:click={() => count++}>
  {count}
</button>

<!-- Parent.svelte -->
<script>
  let value = 0;
</script>
<Counter bind:count={value} />

Event Handling Changes

From createEventDispatcher to Callbacks

createEventDispatcher is deprecated in Svelte 5. Use callback props instead.
<!-- Button.svelte -->
<script>
  import { createEventDispatcher } from 'svelte';
  
  const dispatch = createEventDispatcher();
  
  function handleClick() {
    dispatch('click', { timestamp: Date.now() });
  }
</script>

<button on:click={handleClick}>
  Click me
</button>

<!-- Parent.svelte -->
<script>
  function handleButtonClick(event) {
    console.log('Clicked at:', event.detail.timestamp);
  }
</script>

<Button on:click={handleButtonClick} />

Event Directives

<script>
  let count = 0;
</script>

<button on:click={() => count++}>
  {count}
</button>

<form on:submit|preventDefault={handleSubmit}>
  <input type="text" />
</form>

Component API Changes

Components as Functions

In Svelte 5, components are functions, not class instances. This means:
  • No new Component() syntax
  • No component.$set() or component.$on() methods
  • Use mount() and unmount() for programmatic rendering
import Component from './Component.svelte';

const component = new Component({
  target: document.body,
  props: { name: 'world' }
});

component.$set({ name: 'Svelte' });
component.$on('event', handler);
component.$destroy();

Slots to Snippets

Basic Content Projection

<!-- Card.svelte -->
<div class="card">
  <slot />
</div>

<!-- App.svelte -->
<Card>
  <h1>Hello World</h1>
</Card>

Named Slots

<!-- Modal.svelte -->
<div class="modal">
  <div class="header">
    <slot name="header" />
  </div>
  <div class="body">
    <slot />
  </div>
  <div class="footer">
    <slot name="footer" />
  </div>
</div>

<!-- App.svelte -->
<Modal>
  <h2 slot="header">Title</h2>
  <p>Content</p>
  <button slot="footer">Close</button>
</Modal>

Scoped Slots (Render Props)

<!-- List.svelte -->
<script>
  export let items;
</script>

<ul>
  {#each items as item}
    <li>
      <slot item={item} />
    </li>
  {/each}
</ul>

<!-- App.svelte -->
<List items={users} let:item>
  <strong>{item.name}</strong>
</List>

Lifecycle Changes

<script>
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
  
  onMount(() => {
    console.log('Component mounted');
    
    return () => {
      console.log('Cleanup on unmount');
    };
  });
  
  onDestroy(() => {
    console.log('Component destroying');
  });
  
  beforeUpdate(() => {
    console.log('Before update');
  });
  
  afterUpdate(() => {
    console.log('After update');
  });
</script>

Store Changes

Stores continue to work in Svelte 5, but you may want to migrate to runes for better performance:
<script>
  import { writable } from 'svelte/store';
  
  const count = writable(0);
  
  function increment() {
    count.update(n => n + 1);
  }
</script>

<button on:click={increment}>
  {$count}
</button>

TypeScript Changes

Update your TypeScript types for Svelte 5:
<script lang="ts">
  export let name: string;
  export let age: number = 0;
  
  interface User {
    id: number;
    name: string;
  }
  
  let users: User[] = [];
</script>

Common Migration Patterns

Two-Way Binding

<script>
  let text = '';
</script>

<input bind:value={text} />
<p>{text}</p>

Component Composition

<!-- Wrapper.svelte -->
<script>
  export let title;
</script>

<div class="wrapper">
  <h1>{title}</h1>
  <slot />
</div>

<!-- App.svelte -->
<Wrapper title="My App">
  <p>Content goes here</p>
</Wrapper>

Migration Checklist

  • Update to Svelte 5
  • Replace $: with $derived and $effect
  • Convert export let to $props()
  • Replace createEventDispatcher with callback props
  • Update on: directives to event attributes
  • Convert <slot> to snippets and {@render}
  • Update component instantiation (use mount)
  • Update lifecycle hooks (use $effect where needed)
  • Update TypeScript types
  • Test all components thoroughly
  • Update documentation

Next Steps