Skip to content

Instantly share code, notes, and snippets.

@diramazioni
Created May 10, 2025 10:13
Show Gist options
  • Save diramazioni/ab5ba76e996cf5b1119c9ebd8579123f to your computer and use it in GitHub Desktop.
Save diramazioni/ab5ba76e996cf5b1119c9ebd8579123f to your computer and use it in GitHub Desktop.
svelte5_runes

Svelte 5 Runes Overview


$State



<script> let count = $state(0); // count is reactive </script>

<button onclick={() => count++}> clicks: {count}



<script> let todos = $state([ { done: false, text: 'Learn Svelte' }, { done: false, text: 'Build an app' } ]); function toggle(index) { todos[index].done = !todos[index].done; // Reactivity applies to individual items } </script>
    {#each todos as todo, index}
  • toggle(index)} /> {todo.text}
  • {/each}

<button onclick={() => todos.push({ done: false, text: 'New task' })}> Add Todo



<script> class Todo { done = $state(false); text = $state(''); constructor(text) { this.text = text; } reset = () => { this.text = ''; this.done = false; } } let todo = new Todo('Buy groceries'); </script>

{todo.text} - {todo.done ? 'Done' : 'Not done'}

todo.done = !todo.done}>Toggle todo.reset()}>Reset

<script> let person = $state.raw({ name: 'Alice', age: 30 }); function updatePerson() { // This won't work because `person` is raw (not reactive) person.age += 1; // Instead, we must reassign the whole object person = { ...person, age: person.age + 1 }; } </script>

{person.name} is {person.age} years old.

Increase Age

<script> let counter = $state({ count: 0 }); function logSnapshot() { console.log($state.snapshot(counter)); // Logs a plain object, not a Proxy } </script>

Count: {counter.count}

counter.count++}>Increment Log Snapshot

<script> function add(getA, getB) { return () => getA() + getB(); } let a = $state(1); let b = $state(2); let total = add(() => a, () => b); // Pass getters to maintain reactivity </script>

{a} + {b} = {total()}

a++}>Increment A b++}>Increment B
<script> let name = $state('Alice'); </script>

<input bind:value={ () => name, (v) => name = v.trimStart() } />

Hello, {name}!


$Derived



<script lang="ts"> let count = $state(0); let doubled = $derived(count * 2); // `doubled` automatically updates when `count` changes </script>

<button onclick={() => count++}> Increment

{count} doubled is {doubled}


  • $derived(count * 2) ensures doubled always reflects the latest count.
  • You cannot modify state inside $derived, e.g., count++ inside the expression.

<script lang="ts"> let numbers = $state([1, 2, 3]); // `total` is automatically recalculated when `numbers` change let total = $derived.by(() => { let sum = 0; for (const n of numbers) { sum += n; } return sum; }); </script>

<button onclick={() => numbers.push(numbers.length + 1)}> Add Number

{numbers.join(' + ')} = {total}


  • $derived.by(() => { ... }) is useful when the derived state requires loops or multiple steps.
  • The derived state (total) automatically updates when numbers change.

<script lang="ts"> let count = $state(0); let isEven = $derived(count % 2 === 0); </script>

<button onclick={() => count++}> Increment

{count} is {isEven ? 'Even' : 'Odd'}


  • $derived tracks all synchronously accessed state inside its expression.
  • If count changes, isEven will automatically update.

<script lang="ts"> let count = $state(0); let large = $derived(count > 10); </script>

<button onclick={() => count++}> Increment

Is count large? {large}


  • Svelte only updates when large changes (falsetrue or vice versa).
  • If count changes within the same threshold (e.g., 5 → 6), no update occurs.


$Effect



<script> let size = $state(50); let color = $state('#ff3e00'); let canvas; $effect(() => { const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); // This effect runs whenever `color` or `size` changes context.fillStyle = color; context.fillRect(0, 0, size, size); }); </script>

<script> let count = $state(0); let milliseconds = $state(1000); $effect(() => { const interval = setInterval(() => { count += 1; }, milliseconds); // Cleanup function runs before effect re-runs or on component destroy return () => { clearInterval(interval); }; }); </script>

{count}

<button onclick={() => (milliseconds *= 2)}>Slower <button onclick={() => (milliseconds /= 2)}>Faster



<script> let size = $state(50); let color = $state('#ff3e00'); let canvas; $effect(() => { const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); // This effect re-runs when `color` changes but not when `size` changes context.fillStyle = color; setTimeout(() => { context.fillRect(0, 0, size, size); }, 0); }); </script>

<script> import { tick } from 'svelte'; let div = $state(); let messages = $state([]); $effect.pre(() => { if (!div) return; // Tracks `messages.length` so the effect re-runs when messages change messages.length; // Auto-scroll logic before the DOM updates if (div.offsetHeight + div.scrollTop > div.scrollHeight - 20) { tick().then(() => { div.scrollTo(0, div.scrollHeight); }); } }); </script>
{#each messages as message}

{message}

{/each}

<button onclick={() => messages = [...messages, 'New message']}>Add Message



<script> let count = $state(0); let doubled = $derived(count * 2); // Correct approach // Incorrect approach (causes infinite loops): // $effect(() => { // doubled = count * 2; // }); </script>

{count} doubled is {doubled}

<button onclick={() => count += 1}>Increment




$Bindable



<script lang="ts"> let { value = $bindable(), ...props } = $props(); // `value` is bindable, allowing two-way data flow </script>

<input bind:value={value} {...props} />


<script lang="ts"> import FancyInput from './FancyInput.svelte'; let message = $state('hello'); // Using `$state` to create a reactive state variable </script>

{message}



<script lang="ts"> let { value = $bindable('fallback'), ...props } = $props(); // Default value set to 'fallback' </script>

<input bind:value={value} {...props} />


<script lang="ts"> import FancyInput from './FancyInput.svelte'; </script>



<script lang="ts"> let { value = $bindable('default text'), disabled = $bindable(false), ...props } = $props(); </script>

<input bind:value={value} bind:disabled={disabled} {...props} />


<script lang="ts"> import FancyInput from './FancyInput.svelte'; let message = $state('Editable text'); let isDisabled = $state(false); </script> isDisabled = !isDisabled}> Toggle Disabled
  • The input's value and disabled states can both be bound to the parent.
  • The button toggles the disabled state dynamically.
  • Useful for forms and UI elements requiring controlled states.

$Props



<script lang="ts"> import MyComponent from './MyComponent.svelte'; </script>
<script lang="ts"> let props = $props(); </script>

This component is {props.adjective}



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

This component is {adjective}



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

This component is {adjective}



<script lang="ts"> let { super: trouper = 'lights are gonna find me' } = $props(); </script>

Song lyric: {trouper}



<script lang="ts"> let { a, b, c, ...others } = $props(); </script>

a: {a}, b: {b}, c: {c}

Other props: {JSON.stringify(others)}



<script lang="ts"> import Child from './Child.svelte'; let count = $state(0); </script>

<button onclick={() => count += 1}> clicks (parent): {count}

<Child {count} />


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

<button onclick={() => count += 1}> clicks (child): {count}



<script lang="ts"> import Child from './Child.svelte'; </script>

<Child object={{ count: 0 }} />


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

<button onclick={() => object.count += 1}> clicks: {object.count}



<script lang="ts"> import Child from './Child.svelte'; let object = $state({ count: 0 }); </script>

<Child {object} />


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

<button onclick={() => object.count += 1}> clicks: {object.count}



<script lang="ts"> const uid = $props.id(); </script> First Name:
<label for="{uid}-lastname">Last Name: </label>
<input id="{uid}-lastname" type="text" />


$Host



<svelte:options customElement="my-stepper" />

<script lang="ts"> function dispatch(type) { $host().dispatchEvent(new CustomEvent(type)); // $host() gives access to the custom element itself. // Dispatches 'increment' or 'decrement' events. } </script>

<button onclick={() => dispatch('decrement')}>decrement <button onclick={() => dispatch('increment')}>increment


<script lang="ts"> import './Stepper.svelte'; let count = $state(0); // State variable to track count </script>

<my-stepper ondecrement={() => count -= 1} onincrement={() => count += 1}

count: {count}


  • $host() only works in components compiled as custom elements (<svelte:options customElement="..."/>).
  • This approach allows dispatching custom events from a shadow DOM encapsulated component.
  • Ensure the parent component listens to the dispatched events (ondecrement, onincrement).

$Inspect



<script> let count = $state(0); let message = $state('hello'); $inspect(count, message); // Logs `count` and `message` whenever they change </script>

<button onclick={() => count++}>Increment




<script> let count = $state(0); $inspect(count).with((type, count) => { if (type === 'update') { console.log(`Count updated to: ${count}`); } }); </script>

<button onclick={() => count++}>Increment




<script> let message = $state('Hello'); $inspect(message).with(console.trace); </script>


<script> import { doSomeWork } from './utils'; $effect(() => { $inspect.trace(); // Logs which state changes caused this effect to run doSomeWork(); }); </script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment