Scott Whittaker

Frontend Developer

Day job React | side projects Svelte

Initial modifications when upgrading to Svelte 5

This site is built with Sveltekit and I recently upgraded to Svelte 5, still in beta at the time of writing, and I thought it would be useful to start here with understanding some of the changes that Svelte 5 introduces. This is a simple site so it is a great place to start making some of the changes before moving on to more complex apps.

It is worth noting that with the upgrade to Svelte 5 here, everything still worked without any changes, the previous syntax is still supported at present. But, I might as well start learning the new shiny stuff. Examples below are from this sites codebase.

$props

Previously, props were declared with the export keyword.

<script lang="ts">
    export let tags: string[];
</script>

In svelte 5 we now use the $props rune.

<script lang="ts">
    let { tags } = $props();
</script>

If using typescript, you can declare prop types.

<script lang="ts">
    interface TagProps {
        tags: Array<string> | string,
    }
    let { tags }: TagProps = $props();
</script>

Renaming props.

<script lang="ts">
    interface TagProps {
        tags: Array<string> | string,
    }
    let { tags: myTags }: TagProps = $props();
</script>

One more example showing more props and setting default values. First the older approach.

<script>
    import DualRange from './DualRange.svelte';
    
    export let change;
    export let label1;
    export let label2;
    export let max = 100;
    export let selectedBorderIndex;
    export let value1 = 0;
    export let value2 = 0;
    
    function onChange(v1, v2) {
        change(selectedBorderIndex, v1, v2);
    }	
</script>

<DualRange {label1} {value1} {label2} {value2} {max} change={onChange} />

Refactored for typescript and to use $props()

<script lang="ts">
    import DualRange from './DualRange.svelte';
    
    interface Props {
        change: (index: number, value1: string, value2: string) => void,
        label1: string,
        label2: string,
        max: number,    
        selectedBorderIndex: number
        value1: number,
        value2: number,                        
    }
    
    let { value1 = 0, value2 = 0, label1, label2, max = 100, change, selectedBorderIndex }: Props = $props();
    
    function onChange(v1: string, v2: string) {
        change(selectedBorderIndex, v1, v2);
    }
</script>

<DualRange {label1} {value1} {label2} {value2} {max} change={onChange} />

$state

There are not a lot of instances on this site where state is used (as it is mostly static pages) but here is one simple example. We can see in this example state is declared as a variable: let width = 320;

<script>
    import DualRange from './DualRange.svelte';
    
    export let styles;
    
    let width = 320;
    let height = 240;
    
    function onDualRangeChanged(w, h) {
        width = w;
        height = h;
    }	
</script>

In Svelte 5 we now use the $state rune e.g. let width = $state(320);

<script lang="ts">
    import DualRange from './DualRange.svelte';
    
    let { styles } = $props();
    
    let width = $state(320);
    let height = $state(240);
    
    function onDualRangeChanged(w: number, h: number) {
        width = w;
        height = h;
    }
</script>

What is the difference?

In non-runes mode, a let declaration is treated as reactive state if it is updated at some point. Unlike $state(…), which works anywhere in your app, let only behaves this way at the top level of a component. Svelte 5 preview