Migrate a Svelte 4 component to Svelte 5 runes syntax. Use when asked to migrate, convert, or upgrade a .svelte file to Svelte 5.
87
83%
Does it follow best practices?
Impact
92%
1.06xAverage score across 3 eval scenarios
Passed
No known issues
Migrate individual Svelte files from v4 to v5 runes syntax.
When the user provides a file path, migrate that specific file following the patterns below.
export let → $props()Before:
<script lang="ts">
export let name: string;
export let count = 0;
export let optional: string | undefined = undefined;
let className = '';
export { className as class };
</script>After:
<script lang="ts">
interface Props {
name: string;
count?: number;
optional?: string;
class?: string;
}
let { name, count = 0, optional, class: className = '' }: Props = $props();
</script>let → $state()Only reactive variables that are reassigned or mutated need $state().
Before:
<script lang="ts">
let count = 0;
let items = [];
</script>After:
<script lang="ts">
let count = $state(0);
let items = $state<string[]>([]);
</script>$: → $derived()Before:
<script lang="ts">
$: doubled = count * 2;
$: filtered = items.filter((i) => i.active);
</script>After:
<script lang="ts">
const doubled = $derived(count * 2);
const filtered = $derived(items.filter((i) => i.active));
</script>For complex derivations use $derived.by():
Before:
<script lang="ts">
$: {
let total = 0;
for (const item of items) total += item.value;
sum = total;
}
</script>After:
<script lang="ts">
const sum = $derived.by(() => {
let total = 0;
for (const item of items) total += item.value;
return total;
});
</script>$: statements → $effect()Before:
<script lang="ts">
$: console.log('count changed:', count);
$: if (count > 10) alert('High count!');
$: document.title = `Count: ${count}`;
</script>After:
<script lang="ts">
$effect(() => {
console.log('count changed:', count);
});
$effect(() => {
if (count > 10) alert('High count!');
});
$effect(() => {
document.title = `Count: ${count}`;
});
</script>on:event → oneventBefore:
<button on:click={handleClick}>Click</button>
<button on:click={() => count++}>Increment</button>
<button on:click|preventDefault={submit}>Submit</button>
<input on:input={handleInput} on:focus={handleFocus} />After:
<button onclick={handleClick}>Click</button>
<button onclick={() => count++}>Increment</button>
<button
onclick={(e) => {
e.preventDefault();
submit(e);
}}>Submit</button
>
<input oninput={handleInput} onfocus={handleFocus} />Event modifiers like |preventDefault, |stopPropagation must be handled manually in the handler.
createEventDispatcher → callback propsBefore:
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
change: string;
submit: { data: FormData };
}>();
function handleChange(value: string) {
dispatch('change', value);
}
</script>After:
<script lang="ts">
interface Props {
onchange?: (value: string) => void;
onsubmit?: (data: { data: FormData }) => void;
}
let { onchange, onsubmit }: Props = $props();
function handleChange(value: string) {
onchange?.(value);
}
</script>Parent component changes:
Before: <Child on:change={handler} />
After: <Child onchange={handler} />
on:event → explicit handlerBefore:
<button on:click>Click me</button>After:
<script lang="ts">
interface Props {
onclick?: (e: MouseEvent) => void;
}
let { onclick }: Props = $props();
</script>
<button {onclick}>Click me</button>Default slot:
Before:
<slot />After:
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
children?: Snippet;
}
let { children }: Props = $props();
</script>
{@render children?.()}Named slots:
Before:
<slot name="header" />
<slot />
<slot name="footer" />After:
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
header?: Snippet;
children?: Snippet;
footer?: Snippet;
}
let { header, children, footer }: Props = $props();
</script>
{@render header?.()}
{@render children?.()}
{@render footer?.()}Slots with props (let:):
Before:
<slot item={currentItem} index={i} />
<!-- Usage -->
<List {items} let:item let:index>
<span>{index}: {item.name}</span>
</List>After:
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
children?: Snippet<[{ item: Item; index: number }]>;
}
let { children }: Props = $props();
</script>
{@render children?.({ item: currentItem, index: i })}
<!-- Usage -->
<List {items}>
{#snippet children({ item, index })}
<span>{index}: {item.name}</span>
{/snippet}
</List>bind: → $bindable()Before:
<script lang="ts">
export let value = '';
</script>
<!-- Parent -->
<Input bind:value={name} />After:
<script lang="ts">
interface Props {
value?: string;
}
let { value = $bindable('') }: Props = $props();
</script>
<!-- Parent stays the same -->
<Input bind:value={name} />beforeUpdate/afterUpdate → $effectBefore:
<script lang="ts">
import { beforeUpdate, afterUpdate } from 'svelte';
beforeUpdate(() => {
console.log('before update');
});
afterUpdate(() => {
console.log('after update');
});
</script>After:
<script lang="ts">
$effect.pre(() => {
console.log('before update');
});
$effect(() => {
console.log('after update');
});
</script>IMPORTANT: onMount and onDestroy remain valid and should be preserved!
onMount for initialization logic that should only run once when the component first mounts$effect for reactive side effects that should re-run when dependencies changeonMount to $effect unless the logic genuinely needs to be reactiveExamples:
Keep as onMount:
// ✅ One-time initialization - keep as onMount
onMount(() => {
if (query) {
$filters = toListWorkflowFilters(query, $searchAttributes);
}
});Convert to $effect:
// ✅ Reactive side effect - convert to $effect
$effect(() => {
document.title = `Count: ${count}`; // re-runs when count changes
});<svelte:component> → direct usageBefore:
<svelte:component this={DynamicComponent} {prop} />After:
<DynamicComponent {prop} />Or with conditional:
{#if Component}
<Component {prop} />
{/if}class: Directive → Conditional ClassesThe class: directive still works but consider using conditional expressions:
Before:
<div class:active={isActive} class:disabled>After (either works):
<div class:active={isActive} class:disabled>
<!-- or -->
<div class={`${isActive ? 'active' : ''} ${disabled ? 'disabled' : ''}`}>Before:
import {page} from '$app/stores'After:
import {page} from '$app/state'pnpm check to verify no type errorsonMount and onDestroy - they're still validcontext="module" scripts are now <script module>{#if}, {#each}, {#await} blocks remain unchangedbind: directives on elements remain unchangeduse: action directives remain unchangedtransition:, in:, out:, animate: directives remain unchangedccb4cb7
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.