Disclaimer: I didn’t learn Svelte before v5, so this is an experience report on translating Svelte 4 code to 5.
This is a blind jump into Svelte.
The Basics
Svelte components end in .svelte
like .jsx
or .tsx
. They look more
HTML-like than JavaScript. In comparison, ReactJS is more JavaScript-like than
HTML.
<script>
let { name } = $props();
</script>
<h1>Hello, {name}!</h1>
Typescript
To use TypeScript, add lang=”ts”
to the script tag:
<script lang="ts">
let { name }: {name: string} = $props();
</script>
<h1>Hello, {name}!</h1>
Builtins
Some special forms exist in Svelte 5 files.
$props()
returns an object, similar to React props.$state(initialValue)
declares a variable tracked by Svelte for automatic re-renders likeuseState
, but without explicit setters and getters. Use the variable like normal JavaScript in Svelte. This is called a rune.$derived(expression)
declares a variable to be tracked by Svelte, but its value depends on another rune.$effect(fn)
calls a function for side effects after the HTML is attached to the DOM. Svelte will look for runes in this function to know when to invalidate it. Effects are invoked when a used rune changes.$bindable()
defines a property like giving a mutable pointer. Component consumers can provide a place to store the bind value.$inspect(value)
is a handy way to print values during debugging.
In non-component files
One reason for runes in Svelte 5 is to work seamlessly in TypeScript/JavaScript
files. To do this, rename the file to have a .svelte
extension:
example.js
toexample.svelte.js
to access runes.example.ts
toexample.svelte.ts
to access runes.
Where is the key?
In React, it’s common to have keys to inform reuse:
{items.map((row) => {
<tr key={row.id}>...</tr>
})}
This helps the diffing algorithm determine the minimal DOM operations. In Svelte 5, Proxy objects manage this – so Svelte can see what operations you perform on vanilla JavaScript collections at runtime:
<script lang="ts">
let items = $state([]); // Svelte returns a proxy object to the Array
...
items.push(...); // svelte knows this is an append operation
</script>
{#each items as item}
<tr>...</tr>
{/each}
You can still specify keys via the each syntax:
{#each items as item (item.id)}
<tr>...</tr>
{/each}
Where (item.id) indicates the key expression. Alternatively,
use #key:
{#key count}
The count is {count}
{/key}
Rerenders?
Anyone experienced with React knows when re-renders occur. It’s a bane of performance debugging in React due to JavaScript’s mutable nature and poor equality semantics (e.g., functions). I guess this means these aren’t an issue, since there’s no Svelte documentation on this.
Integration with Third-Parties
For third-party libraries, it’s straightforward to attach onMount behaviors
using $effect()
with a return value for unmount behavior.
<script lang="ts">
import { createPlugin, destroyPlugin } from 'randomJSLibrary';
let element: HTMLElement;
$effect(() => {
createPlugin({target: element});
return () => {
destroyPlugin();
}
})
</script>
<div bind:this={element}></div>
Special Elements
Svelte has special elements for
activities unusual for traditional templating/layout engines. These tags are
namespaced with svelte:
<svelte:head>
allows adding elements in the page’s .<svelte:self>
allows recursive components.<svelte:window>
,<svelte:document>
, and<svelte:body>
allow attaching handlers or behaviors to these page-level elements.
Translating Svelte 4 code
Svelte 5 or Svelte 4 syntax is per-file. It is detected automatically based on API usage.
Slots
<slot />
is replaced with {@render children()}
. Like:
<script lang="ts">
import type { Snippet } from 'svelte';
let { children }: { children?: Snippet } = $props();
</script>
{@render children?.()}
Replace <slot name=”foo” />
attributes with {#snippet foo()}
, where foo
.
Props
All export let myPropertyName
statements can be refactored to use destructure
from $props()
.
svelte:component
This has been replaced by using the component directly in code:
<svelte:component this={myTag}></svelte:component>
Is now:
<myTag></myTag>
// sometimes, you may need to define a variable before using:
{@const myTag = item.Element}
<myTag></myTag>