Introduction to Svelte

  1. What Is Svelte?



  2. How Svelte Works

  3. <script>
        let message = "Hello Svelte!";
    </script>
    
    <h1>{message}</h1>
    
    <style>
        h1 {
            color: #ff3e00;
        }
    </style>
    


  4. Getting Started (Project Setup)

  5. npx sv create my-app
    cd my-app/
    npm install
    npm run dev
    
    npm create vite@latest my-app -- --template svelte
    cd my-app/
    npm install
    npm run dev
    


  6. Core Concepts of Svelte

    1. Reactivity

    2. <script>
          let count = 0;
      
          function increment() {
              count += 1;  // This automatically updates the DOM
          }
      </script>
      
      <button on:click={increment}>
          Clicked {count} times
      </button>
      


    3. Reactive Declarations ($:)

    4. <script>
          let count = 0;
          $: doubled = count * 2;
          $: console.log("Count is now", count);
      </script>
      
      <p>{count} doubled is {doubled}</p>
      


    5. Event Handling
    6. <button on:click={handleClick}>Click me</button>
      
      <!-- With inline handler -->
      <button on:click={() => count++}>Increase</button>
      
      <!-- With modifiers -->
      <button on:click|once|preventDefault={handleClick}>Submit</button>
      


    7. Two-Way Binding with bind:
    8. <script>
          let name = "";
      </script>
      
      <input bind:value={name} />
      <p>Hello {name}!</p>
      


    9. Conditionals and Loops
    10. <!-- Conditionals -->
      {#if loggedIn}
          <p>Welcome back!</p>
      {:else}
          <p>Please log in.</p>
      {/if}
      
      <!-- Loops -->
      {#each items as item, index (item.id)}
          <li>{index}: {item.name}</li>
      {/each}
      


  7. Component Structure

  8. <!-- Greeting.svelte -->
    <script>
        export let name = "World";
    </script>
    
    <h1>Hello {name}!</h1>
    
    <style>
        h1 {
            font-family: Georgia, serif;
            color: #333;
        }
    </style>
    


  9. Using Components

  10. <script>
        import Greeting from "./Greeting.svelte";
    </script>
    
    <Greeting name="Svelte" />
    <Greeting />  <!-- Uses default "World" -->
    


  11. Key Differences from Other Frameworks

  12. Feature Svelte Vue / React
    Compilation Compiles to vanilla JS at build time Ships a runtime library to the browser
    Virtual DOM No virtual DOM Uses virtual DOM for diffing
    Reactivity Built into the language ($:) Requires hooks or reactive APIs
    Bundle Size Typically smaller Includes framework runtime
    Learning Curve Closer to vanilla HTML/CSS/JS Framework-specific patterns


  13. Svelte 5 Runes (New Reactivity)

  14. <script>
        let count = $state(0);
        let doubled = $derived(count * 2);
    
        function increment() {
            count++;
        }
    </script>
    
    <button onclick={increment}>
        {count} × 2 = {doubled}
    </button>
    



Svelte Reactivity

  1. What Is Reactivity in Svelte?



  2. Assignment-Based Reactivity

  3. <script>
        let count = 0;
    
        function increment() {
            count = count + 1;  // Assignment triggers update
        }
    </script>
    
    <button on:click={increment}>
        Count: {count}
    </button>
    
    count += 1;   // Reactive
    count++;      // Reactive
    count--;      // Reactive
    


  4. Reactivity with Arrays and Objects

  5. <script>
        let items = ["Apple", "Banana"];
    
        function addItem() {
            items.push("Cherry");  // This does NOT trigger update!
        }
    </script>
    
    <script>
        let items = ["Apple", "Banana"];
    
        function addItem() {
            items.push("Cherry");
            items = items;  // Reassignment triggers update
        }
    
        // Or more idiomatically:
        function addItemBetter() {
            items = [...items, "Cherry"];  // Spread creates new array
        }
    </script>
    
    let user = { name: "Alice", age: 25 };
    
    user.age = 26;      // Does NOT trigger update
    user = user;        // Triggers update
    
    // Or:
    user = { ...user, age: 26 };  // Triggers update
    


  6. Reactive Declarations ($:)

  7. <script>
        let count = 0;
        $: doubled = count * 2;
        $: quadrupled = doubled * 2;
    </script>
    
    <p>{count} × 2 = {doubled}</p>
    <p>{count} × 4 = {quadrupled}</p>
    


  8. Reactive Statements

  9. <script>
        let count = 0;
    
        $: console.log("Count changed to:", count);
    
        $: if (count >= 10) {
            alert("Count is getting high!");
            count = 0;
        }
    
        $: {
            console.log("Running reactive block");
            console.log("Current count:", count);
        }
    </script>
    


  10. Reactive Stores


    1. writable — Read and write
    2. // stores.js
      import { writable } from "svelte/store";
      
      export const count = writable(0);
      
      <script>
          import { count } from "./stores.js";
      
          function increment() {
              count.update(n => n + 1);
          }
      
          function reset() {
              count.set(0);
          }
      </script>
      


    3. readable — Read only
    4. import { readable } from "svelte/store";
      
      export const time = readable(new Date(), function start(set) {
          const interval = setInterval(() => {
              set(new Date());
          }, 1000);
      
          return function stop() {
              clearInterval(interval);
          };
      });
      


    5. derived — Derived from other stores
    6. import { derived } from "svelte/store";
      import { count } from "./stores.js";
      
      export const doubled = derived(count, $count => $count * 2);
      
      // Derived from multiple stores:
      export const total = derived(
          [storeA, storeB],
          ([$a, $b]) => $a + $b
      );
      


  11. Auto-Subscription with $ Prefix

  12. <script>
        import { count } from "./stores.js";
    </script>
    
    <p>The count is {$count}</p>
    
    <button on:click={() => $count++}>
        Increment
    </button>
    
    <script>
        import { name } from "./stores.js";
    </script>
    
    <input bind:value={$name}>
    


  13. Custom Stores

  14. import { writable } from "svelte/store";
    
    function createCounter() {
        const { subscribe, set, update } = writable(0);
    
        return {
            subscribe,
            increment: () => update(n => n + 1),
            decrement: () => update(n => n - 1),
            reset: () => set(0)
        };
    }
    
    export const counter = createCounter();
    
    <script>
        import { counter } from "./stores.js";
    </script>
    
    <p>{$counter}</p>
    <button on:click={counter.increment}>+</button>
    <button on:click={counter.decrement}>-</button>
    <button on:click={counter.reset}>Reset</button>
    


  15. Svelte 5 Runes (New Reactivity System)


    1. $state — Reactive State
    2. <script>
          let count = $state(0);
      
          function increment() {
              count++;  // Directly mutate, it's reactive!
          }
      </script>
      
      <button onclick={increment}>
          Count: {count}
      </button>
      
      let items = $state(["Apple", "Banana"]);
      items.push("Cherry");  // This IS reactive in Svelte 5!
      
      let user = $state({ name: "Alice", age: 25 });
      user.age = 26;  // This IS reactive in Svelte 5!
      


    3. $derived — Computed Values
    4. <script>
          let count = $state(0);
          let doubled = $derived(count * 2);
          let quadrupled = $derived(doubled * 2);
      </script>
      
      <p>{count} × 2 = {doubled}</p>
      <p>{count} × 4 = {quadrupled}</p>
      


    5. $effect — Side Effects
    6. <script>
          let count = $state(0);
      
          $effect(() => {
              console.log("Count is now:", count);
          });
      
          $effect(() => {
              // Runs when component mounts
              console.log("Mounted!");
      
              return () => {
                  // Cleanup when component unmounts
                  console.log("Unmounted!");
              };
          });
      </script>
      


    7. $props — Component Props
    8. <script>
          let { name, age = 18 } = $props();
      </script>
      
      <p>{name} is {age} years old.</p>
      


  16. Comparison: Svelte 4 vs Svelte 5 Reactivity

  17. Feature Svelte 4 Svelte 5 (Runes)
    Reactive variable let count = 0; let count = $state(0);
    Computed value $: doubled = count * 2; let doubled = $derived(count * 2);
    Side effect $: console.log(count); $effect(() => console.log(count));
    Props export let name; let { name } = $props();
    Array/Object mutation Requires reassignment Direct mutation works
    Event handlers on:click={fn} onclick={fn}



Svelte Props

  1. What Are Props?



  2. Declaring Props (Svelte 4)

  3. <!-- Greeting.svelte -->
    <script>
        export let name;
    </script>
    
    <h1>Hello {name}!</h1>
    
    <!-- App.svelte -->
    <script>
        import Greeting from "./Greeting.svelte";
    </script>
    
    <Greeting name="Alice" />
    <Greeting name="Bob" />
    


  4. Default Values

  5. <script>
        export let name = "World";
        export let age = 18;
        export let active = false;
    </script>
    
    <p>{name} is {age} years old.</p>
    
    <!-- Using defaults -->
    <Greeting />                    <!-- name="World", age=18 -->
    <Greeting name="Alice" />       <!-- name="Alice", age=18 -->
    <Greeting name="Bob" age={25} />
    


  6. Passing Different Data Types

  7. <!-- String -->
    <User name="Alice" />
    
    <!-- Number -->
    <User age={25} />
    
    <!-- Boolean -->
    <User active={true} />
    <User active />              <!-- Shorthand for active={true} -->
    
    <!-- Array -->
    <List items={["Apple", "Banana", "Cherry"]} />
    
    <!-- Object -->
    <Profile user={{ name: "Alice", age: 25 }} />
    
    <!-- Variable -->
    <script>
        let username = "Alice";
    </script>
    <User name={username} />
    


  8. Shorthand Props

  9. <script>
        let name = "Alice";
        let age = 25;
    </script>
    
    <!-- Instead of this: -->
    <User name={name} age={age} />
    
    <!-- Use shorthand: -->
    <User {name} {age} />
    


  10. Spread Props

  11. <script>
        import User from "./User.svelte";
    
        const userData = {
            name: "Alice",
            age: 25,
            email: "alice@example.com"
        };
    </script>
    
    <!-- Instead of: -->
    <User name={userData.name} age={userData.age} email={userData.email} />
    
    <!-- Use spread: -->
    <User {...userData} />
    


  12. Receiving All Props with $$props and $$restProps

  13. <script>
        export let name;
        export let age;
    
        // $$props = { name: "...", age: ..., ...anyOtherProps }
        console.log($$props);
    </script>
    
    <!-- Button.svelte -->
    <script>
        export let variant = "primary";
        // Any other props (class, id, disabled, etc.) go to $$restProps
    </script>
    
    <button class="btn btn-{variant}" {...$$restProps}>
        <slot />
    </button>
    
    <!-- Usage -->
    <Button variant="danger" disabled id="submit-btn">
        Submit
    </Button>
    


  14. Reactive Props

  15. <!-- Parent.svelte -->
    <script>
        import Counter from "./Counter.svelte";
        let count = 0;
    </script>
    
    <button on:click={() => count++}>Increment in Parent</button>
    <Counter value={count} />
    
    <!-- Counter.svelte -->
    <script>
        export let value;
        $: console.log("Value changed to:", value);
    </script>
    
    <p>Count: {value}</p>
    


  16. Readonly Props (One-Way Binding)

  17. <!-- Child.svelte -->
    <script>
        export let count = 0;
    
        function increment() {
            count++;  // Only changes local copy, not parent's value
        }
    </script>
    
    <button on:click={increment}>{count}</button>
    


  18. Props with TypeScript (Svelte 4)

  19. <script lang="ts">
        export let name: string;
        export let age: number = 18;
        export let active: boolean = false;
        export let items: string[] = [];
        export let user: { name: string; email: string } | null = null;
    </script>
    
    <script lang="ts">
        interface User {
            id: number;
            name: string;
            email: string;
        }
    
        export let user: User;
        export let users: User[] = [];
    </script>
    


  20. Svelte 5: Props with $props

  21. <!-- Greeting.svelte (Svelte 5) -->
    <script>
        let { name } = $props();
    </script>
    
    <h1>Hello {name}!</h1>
    
    <script>
        let { name = "World", age = 18, active = false } = $props();
    </script>
    
    <script>
        let { name, age, ...rest } = $props();
    </script>
    
    <div {...rest}>
        {name} is {age}
    </div>
    


  22. Svelte 5: Props with TypeScript

  23. <script lang="ts">
        interface Props {
            name: string;
            age?: number;
            active?: boolean;
        }
    
        let { name, age = 18, active = false }: Props = $props();
    </script>
    
    <script lang="ts">
        import type { HTMLButtonAttributes } from "svelte/elements";
    
        interface Props extends HTMLButtonAttributes {
            variant?: "primary" | "secondary" | "danger";
        }
    
        let { variant = "primary", ...rest }: Props = $props();
    </script>
    
    <button class="btn btn-{variant}" {...rest}>
        <slot />
    </button>
    


  24. Comparison: Svelte 4 vs Svelte 5 Props

  25. Feature Svelte 4 Svelte 5
    Basic prop export let name; let { name } = $props();
    Default value export let name = "World"; let { name = "World" } = $props();
    Multiple props Multiple export let lines Destructure from $props()
    Rest props $$restProps let { a, ...rest } = $props();
    All props $$props let props = $props();



Svelte Logic

  1. What Is Svelte Logic?



  2. Conditional Rendering with {#if}

  3. <script lang="ts">
        let loggedIn: boolean = false;
    </script>
    
    {#if loggedIn}
        <p>Welcome back!</p>
    {/if}
    
    <button onclick={() => loggedIn = !loggedIn}>
        Toggle Login
    </button>
    


  4. {:else} Block

  5. <script lang="ts">
        let loggedIn: boolean = false;
    </script>
    
    {#if loggedIn}
        <p>Welcome back, user!</p>
        <button onclick={() => loggedIn = false}>Log Out</button>
    {:else}
        <p>Please log in.</p>
        <button onclick={() => loggedIn = true}>Log In</button>
    {/if}
    


  6. {:else if} Block

  7. <script lang="ts">
        let score: number = 75;
    </script>
    
    {#if score >= 90}
        <p>Grade: A</p>
    {:else if score >= 80}
        <p>Grade: B</p>
    {:else if score >= 70}
        <p>Grade: C</p>
    {:else if score >= 60}
        <p>Grade: D</p>
    {:else}
        <p>Grade: F</p>
    {/if}
    


  8. Nested Conditionals

  9. <script lang="ts">
        let loggedIn: boolean = true;
        let isAdmin: boolean = true;
    </script>
    
    {#if loggedIn}
        <p>Welcome!</p>
        {#if isAdmin}
            <p>You have admin privileges.</p>
            <button>Access Admin Panel</button>
        {:else}
            <p>You are a regular user.</p>
        {/if}
    {:else}
        <p>Please log in.</p>
    {/if}
    


  10. Looping with {#each}

  11. <script lang="ts">
        let fruits: string[] = ["Apple", "Banana", "Cherry"];
    </script>
    
    <ul>
        {#each fruits as fruit}
            <li>{fruit}</li>
        {/each}
    </ul>
    


  12. {#each} with Index

  13. <script lang="ts">
        let fruits: string[] = ["Apple", "Banana", "Cherry"];
    </script>
    
    <ul>
        {#each fruits as fruit, index}
            <li>{index + 1}. {fruit}</li>
        {/each}
    </ul>
    


  14. Keyed {#each} Blocks

  15. <script lang="ts">
        interface Todo {
            id: number;
            text: string;
            done: boolean;
        }
    
        let todos: Todo[] = [
            { id: 1, text: "Learn Svelte", done: false },
            { id: 2, text: "Build an app", done: false },
            { id: 3, text: "Deploy", done: false }
        ];
    </script>
    
    <ul>
        {#each todos as todo (todo.id)}
            <li>{todo.text}</li>
        {/each}
    </ul>
    


  16. {#each} with Destructuring

  17. <script lang="ts">
        interface User {
            id: number;
            name: string;
            email: string;
        }
    
        let users: User[] = [
            { id: 1, name: "Alice", email: "alice@example.com" },
            { id: 2, name: "Bob", email: "bob@example.com" }
        ];
    </script>
    
    <ul>
        {#each users as { id, name, email } (id)}
            <li>{name} - {email}</li>
        {/each}
    </ul>
    


  18. {:else} in {#each}

  19. <script lang="ts">
        let todos: string[] = [];
    </script>
    
    <ul>
        {#each todos as todo}
            <li>{todo}</li>
        {:else}
            <li>No todos yet. Add one!</li>
        {/each}
    </ul>
    


  20. Iterating Over Objects

  21. <script lang="ts">
        const scores: Record<string, number> = {
            Alice: 95,
            Bob: 82,
            Charlie: 78
        };
    </script>
    
    <ul>
        {#each Object.entries(scores) as [name, score]}
            <li>{name}: {score}</li>
        {/each}
    </ul>
    


  22. Handling Promises with {#await}

  23. <script lang="ts">
        interface User {
            id: number;
            name: string;
        }
    
        async function fetchUser(): Promise<User> {
            const response = await fetch("https://api.example.com/user/1");
            return response.json();
        }
    
        let userPromise: Promise<User> = fetchUser();
    </script>
    
    {#await userPromise}
        <p>Loading...</p>
    {:then user}
        <p>Hello, {user.name}!</p>
    {:catch error}
        <p>Error: {error.message}</p>
    {/await}
    


  24. {#await} Without Loading State

  25. <script lang="ts">
        let dataPromise: Promise<string> = Promise.resolve("Hello!");
    </script>
    
    {#await dataPromise then data}
        <p>{data}</p>
    {/await}
    


  26. {#await} Without Catch

  27. <script lang="ts">
        interface Post {
            title: string;
            body: string;
        }
    
        async function fetchPost(): Promise<Post> {
            const res = await fetch("https://api.example.com/posts/1");
            return res.json();
        }
    
        let postPromise: Promise<Post> = fetchPost();
    </script>
    
    {#await postPromise}
        <p>Loading post...</p>
    {:then post}
        <h2>{post.title}</h2>
        <p>{post.body}</p>
    {/await}
    


  28. Refreshing {#await} Data

  29. <script lang="ts">
        interface User {
            id: number;
            name: string;
        }
    
        async function fetchRandomUser(): Promise<User> {
            const id = Math.floor(Math.random() * 10) + 1;
            const res = await fetch(`https://api.example.com/users/${id}`);
            return res.json();
        }
    
        let userPromise: Promise<User> = fetchRandomUser();
    
        function refresh(): void {
            userPromise = fetchRandomUser();  // Reassign to refetch
        }
    </script>
    
    <button onclick={refresh}>Load Random User</button>
    
    {#await userPromise}
        <p>Loading...</p>
    {:then user}
        <p>{user.name}</p>
    {:catch error}
        <p>Failed to load user.</p>
    {/await}
    


  30. Forcing Re-render with {#key}

  31. <script lang="ts">
        let userId: number = 1;
    </script>
    
    <input type="number" bind:value={userId} min="1" />
    
    {#key userId}
        <UserProfile id={userId} />
    {/key}
    


  32. {#key} for Animations

  33. <script lang="ts">
        import { fade } from "svelte/transition";
    
        let count: number = 0;
    </script>
    
    <button onclick={() => count++}>Increment</button>
    
    {#key count}
        <p transition:fade>{count}</p>
    {/key}
    


  34. Combining Logic Blocks

  35. <script lang="ts">
        interface Post {
            id: number;
            title: string;
            published: boolean;
        }
    
        async function fetchPosts(): Promise<Post[]> {
            const res = await fetch("https://api.example.com/posts");
            return res.json();
        }
    
        let postsPromise: Promise<Post[]> = fetchPosts();
        let showOnlyPublished: boolean = false;
    </script>
    
    <label>
        <input type="checkbox" bind:checked={showOnlyPublished} />
        Show only published
    </label>
    
    {#await postsPromise}
        <p>Loading posts...</p>
    {:then posts}
        <ul>
            {#each posts as post (post.id)}
                {#if !showOnlyPublished || post.published}
                    <li>
                        {post.title}
                        {#if !post.published}
                            <span>(Draft)</span>
                        {/if}
                    </li>
                {/if}
            {/each}
        </ul>
    {:catch}
        <p>Failed to load posts.</p>
    {/await}
    


  36. Logic Blocks Summary

  37. Block Purpose Syntax
    {#if} Conditional rendering {#if condition}...{/if}
    {:else} Else branch {#if}...{:else}...{/if}
    {:else if} Else-if branch {#if}...{:else if}...{/if}
    {#each} Loop over arrays {#each array as item}...{/each}
    {#each} + key Keyed loop {#each array as item (key)}...{/each}
    {#await} Handle promises {#await promise}...{:then}...{:catch}...{/await}
    {#key} Force re-render {#key value}...{/key}



Svelte Events

  1. What Are Events in Svelte?



  2. DOM Event Handling (Svelte 4)

  3. <script lang="ts">
        let count: number = 0;
    
        function handleClick(): void {
            count++;
        }
    </script>
    
    <button on:click={handleClick}>
        Clicked {count} times
    </button>
    
    <!-- Mouse events -->
    <button on:click={handleClick}>Click</button>
    <div on:dblclick={handleDoubleClick}>Double click</div>
    <div on:mouseenter={handleMouseEnter}>Hover me</div>
    <div on:mouseleave={handleMouseLeave}>Leave me</div>
    <div on:mousemove={handleMouseMove}>Move inside</div>
    
    <!-- Keyboard events -->
    <input on:keydown={handleKeyDown} />
    <input on:keyup={handleKeyUp} />
    <input on:keypress={handleKeyPress} />
    
    <!-- Form events -->
    <input on:input={handleInput} />
    <input on:change={handleChange} />
    <input on:focus={handleFocus} />
    <input on:blur={handleBlur} />
    <form on:submit={handleSubmit}>...</form>
    
    <!-- Other events -->
    <div on:scroll={handleScroll}>...</div>
    <img on:load={handleLoad} on:error={handleError} />
    


  4. DOM Event Handling (Svelte 5)

  5. <script lang="ts">
        let count: number = $state(0);
    
        function handleClick(): void {
            count++;
        }
    </script>
    
    <button onclick={handleClick}>
        Clicked {count} times
    </button>
    
    <!-- Svelte 5 event syntax -->
    <button onclick={handleClick}>Click</button>
    <div ondblclick={handleDoubleClick}>Double click</div>
    <input onkeydown={handleKeyDown} />
    <input oninput={handleInput} />
    <form onsubmit={handleSubmit}>...</form>
    


  6. Inline Event Handlers

  7. <script lang="ts">
        let count: number = $state(0);
        let message: string = $state("");
    </script>
    
    <!-- Svelte 5 syntax -->
    <button onclick={() => count++}>Increment</button>
    <button onclick={() => count = 0}>Reset</button>
    <button onclick={() => alert("Hello!")}>Alert</button>
    
    <input oninput={(e) => message = e.currentTarget.value} />
    
    <!-- Svelte 4 syntax -->
    <button on:click={() => count++}>Increment</button>
    <button on:click={() => count = 0}>Reset</button>
    


  8. The Event Object

  9. <script lang="ts">
        function handleClick(event: MouseEvent): void {
            console.log("Clicked at:", event.clientX, event.clientY);
            console.log("Target:", event.target);
            console.log("Current target:", event.currentTarget);
        }
    
        function handleKeyDown(event: KeyboardEvent): void {
            console.log("Key pressed:", event.key);
            console.log("Key code:", event.code);
            if (event.key === "Enter") {
                console.log("Enter pressed!");
            }
        }
    
        function handleInput(event: Event): void {
            const target = event.currentTarget as HTMLInputElement;
            console.log("Input value:", target.value);
        }
    </script>
    
    <button onclick={handleClick}>Click me</button>
    <input onkeydown={handleKeyDown} />
    <input oninput={handleInput} />
    


  10. Event Modifiers (Svelte 4)

  11. <!-- preventDefault: Prevents default browser action -->
    <form on:submit|preventDefault={handleSubmit}>
        <button type="submit">Submit</button>
    </form>
    
    <!-- stopPropagation: Stops event from bubbling up -->
    <div on:click={handleOuter}>
        <button on:click|stopPropagation={handleInner}>Click</button>
    </div>
    
    <!-- once: Handler runs only once -->
    <button on:click|once={handleClick}>Click once</button>
    
    <!-- capture: Use capture phase instead of bubble -->
    <div on:click|capture={handleClick}>...</div>
    
    <!-- self: Only trigger if event.target is the element itself -->
    <div on:click|self={handleClick}>
        <button>Clicking here won't trigger parent</button>
    </div>
    
    <!-- passive: Improves scroll performance -->
    <div on:scroll|passive={handleScroll}>...</div>
    
    <!-- nonpassive: Explicitly not passive -->
    <div on:touchstart|nonpassive={handleTouch}>...</div>
    
    <!-- trusted: Only trigger for user-initiated events -->
    <button on:click|trusted={handleClick}>...</button>
    
    <form on:submit|preventDefault|stopPropagation={handleSubmit}>
        ...
    </form>
    
    <button on:click|once|capture={handleClick}>...</button>
    


  12. Event Modifiers (Svelte 5)

  13. <script lang="ts">
        function handleSubmit(event: SubmitEvent): void {
            event.preventDefault();
            // Handle form submission
        }
    
        function handleInnerClick(event: MouseEvent): void {
            event.stopPropagation();
            // Handle click
        }
    </script>
    
    <form onsubmit={handleSubmit}>
        <button type="submit">Submit</button>
    </form>
    
    <div onclick={handleOuter}>
        <button onclick={handleInnerClick}>Click</button>
    </div>
    
    <script lang="ts">
        let clicked: boolean = $state(false);
    
        function handleOnce(): void {
            if (clicked) return;
            clicked = true;
            console.log("This runs only once");
        }
    </script>
    
    <button onclick={handleOnce}>Click once</button>
    
    <!-- Capture phase -->
    <div onclickcapture={handleClick}>...</div>
    


  14. Component Events (Svelte 4)

  15. <!-- Button.svelte -->
    <script lang="ts">
        import { createEventDispatcher } from "svelte";
    
        const dispatch = createEventDispatcher<{
            click: void;
            customEvent: { message: string };
        }>();
    
        function handleClick(): void {
            dispatch("click");
            dispatch("customEvent", { message: "Hello from child!" });
        }
    </script>
    
    <button on:click={handleClick}>
        <slot />
    </button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Button from "./Button.svelte";
    
        function handleCustomEvent(event: CustomEvent<{ message: string }>): void {
            console.log(event.detail.message);
        }
    </script>
    
    <Button on:click={() => console.log("Clicked!")} on:customEvent={handleCustomEvent}>
        Click me
    </Button>
    


  16. Component Events (Svelte 5)

  17. <!-- Button.svelte -->
    <script lang="ts">
        interface Props {
            onclick?: () => void;
            onCustomEvent?: (data: { message: string }) => void;
            children?: any;
        }
    
        let { onclick, onCustomEvent, children }: Props = $props();
    
        function handleClick(): void {
            onclick?.();
            onCustomEvent?.({ message: "Hello from child!" });
        }
    </script>
    
    <button onclick={handleClick}>
        {@render children?.()}
    </button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Button from "./Button.svelte";
    
        function handleCustomEvent(data: { message: string }): void {
            console.log(data.message);
        }
    </script>
    
    <Button onclick={() => console.log("Clicked!")} onCustomEvent={handleCustomEvent}>
        Click me
    </Button>
    


  18. Event Forwarding (Svelte 4)

  19. <!-- Button.svelte -->
    <script lang="ts">
        export let variant: string = "primary";
    </script>
    
    <!-- Forward click event to parent -->
    <button class="btn btn-{variant}" on:click>
        <slot />
    </button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Button from "./Button.svelte";
    </script>
    
    <Button on:click={() => console.log("Button clicked!")}>
        Click me
    </Button>
    
    <input
        on:input
        on:change
        on:focus
        on:blur
        on:keydown
    />
    


  20. Event Forwarding (Svelte 5)

  21. <!-- Button.svelte -->
    <script lang="ts">
        import type { HTMLButtonAttributes } from "svelte/elements";
    
        interface Props extends HTMLButtonAttributes {
            variant?: string;
        }
    
        let { variant = "primary", children, ...rest }: Props = $props();
    </script>
    
    <button class="btn btn-{variant}" {...rest}>
        {@render children?.()}
    </button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Button from "./Button.svelte";
    </script>
    
    <Button onclick={() => console.log("Clicked!")} onmouseenter={() => console.log("Hovered!")}>
        Click me
    </Button>
    


  22. Typed Custom Events (Svelte 4)

  23. <!-- SearchBox.svelte -->
    <script lang="ts">
        import { createEventDispatcher } from "svelte";
    
        interface SearchEvents {
            search: string;
            clear: void;
            select: { id: number; label: string };
        }
    
        const dispatch = createEventDispatcher<SearchEvents>();
    
        let query: string = "";
    
        function handleSearch(): void {
            dispatch("search", query);
        }
    
        function handleClear(): void {
            query = "";
            dispatch("clear");
        }
    
        function handleSelect(id: number, label: string): void {
            dispatch("select", { id, label });
        }
    </script>
    
    <input bind:value={query} on:input={handleSearch} />
    <button on:click={handleClear}>Clear</button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import SearchBox from "./SearchBox.svelte";
    
        function onSearch(event: CustomEvent<string>): void {
            console.log("Searching for:", event.detail);
        }
    
        function onClear(): void {
            console.log("Search cleared");
        }
    
        function onSelect(event: CustomEvent<{ id: number; label: string }>): void {
            console.log("Selected:", event.detail.id, event.detail.label);
        }
    </script>
    
    <SearchBox on:search={onSearch} on:clear={onClear} on:select={onSelect} />
    


  24. Typed Callback Props (Svelte 5)

  25. <!-- SearchBox.svelte -->
    <script lang="ts">
        interface Props {
            onSearch?: (query: string) => void;
            onClear?: () => void;
            onSelect?: (item: { id: number; label: string }) => void;
        }
    
        let { onSearch, onClear, onSelect }: Props = $props();
    
        let query: string = $state("");
    
        function handleInput(): void {
            onSearch?.(query);
        }
    
        function handleClear(): void {
            query = "";
            onClear?.();
        }
    </script>
    
    <input bind:value={query} oninput={handleInput} />
    <button onclick={handleClear}>Clear</button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import SearchBox from "./SearchBox.svelte";
    
        function handleSearch(query: string): void {
            console.log("Searching for:", query);
        }
    
        function handleSelect(item: { id: number; label: string }): void {
            console.log("Selected:", item.id, item.label);
        }
    </script>
    
    <SearchBox onSearch={handleSearch} onClear={() => console.log("Cleared")} onSelect={handleSelect} />
    


  26. Handling Window and Document Events

  27. <script lang="ts">
        let innerWidth: number = $state(0);
        let innerHeight: number = $state(0);
    
        function handleKeyDown(event: KeyboardEvent): void {
            if (event.key === "Escape") {
                console.log("Escape pressed!");
            }
        }
    
        function handleVisibilityChange(): void {
            console.log("Visibility:", document.visibilityState);
        }
    </script>
    
    <!-- Window events -->
    <svelte:window
        bind:innerWidth
        bind:innerHeight
        onkeydown={handleKeyDown}
        onresize={() => console.log("Resized!")}
    />
    
    <!-- Document events -->
    <svelte:document onvisibilitychange={handleVisibilityChange} />
    
    <p>Window size: {innerWidth} x {innerHeight}</p>
    


  28. Handling Body Events

  29. <script lang="ts">
        let mouseX: number = $state(0);
        let mouseY: number = $state(0);
    
        function handleMouseMove(event: MouseEvent): void {
            mouseX = event.clientX;
            mouseY = event.clientY;
        }
    </script>
    
    <svelte:body onmousemove={handleMouseMove} />
    
    <p>Mouse position: {mouseX}, {mouseY}</p>
    


  30. Event Syntax Comparison

  31. Feature Svelte 4 Svelte 5
    DOM event on:click={handler} onclick={handler}
    Inline handler on:click={() => ...} onclick={() => ...}
    Modifiers on:click|preventDefault event.preventDefault()
    Event forwarding on:click (no value) {...rest} spread
    Component events createEventDispatcher Callback props
    Listen to component on:customEvent={handler} onCustomEvent={handler}
    Capture phase on:click|capture onclickcapture



Svelte Bindings

  1. What Are Bindings?



  2. Basic Input Binding

  3. <script lang="ts">
        let name: string = $state("");
    </script>
    
    <input type="text" bind:value={name} />
    <p>Hello, {name || "stranger"}!</p>
    


  4. Text Input Types

  5. <script lang="ts">
        let text: string = $state("");
        let email: string = $state("");
        let password: string = $state("");
        let search: string = $state("");
        let url: string = $state("");
        let tel: string = $state("");
    </script>
    
    <input type="text" bind:value={text} />
    <input type="email" bind:value={email} />
    <input type="password" bind:value={password} />
    <input type="search" bind:value={search} />
    <input type="url" bind:value={url} />
    <input type="tel" bind:value={tel} />
    


  6. Numeric Input Binding

  7. <script lang="ts">
        let quantity: number = $state(1);
        let volume: number = $state(50);
    </script>
    
    <input type="number" bind:value={quantity} min="0" max="100" />
    <p>Quantity: {quantity} (type: {typeof quantity})</p>
    
    <input type="range" bind:value={volume} min="0" max="100" />
    <p>Volume: {volume}%</p>
    


  8. Checkbox Binding

  9. <script lang="ts">
        let agreed: boolean = $state(false);
        let subscribed: boolean = $state(true);
    </script>
    
    <label>
        <input type="checkbox" bind:checked={agreed} />
        I agree to the terms
    </label>
    
    <label>
        <input type="checkbox" bind:checked={subscribed} />
        Subscribe to newsletter
    </label>
    
    <p>Agreed: {agreed}, Subscribed: {subscribed}</p>
    


  10. Checkbox Group Binding

  11. <script lang="ts">
        let selectedFruits: string[] = $state([]);
    
        const fruits: string[] = ["Apple", "Banana", "Cherry", "Mango"];
    </script>
    
    {#each fruits as fruit}
        <label>
            <input type="checkbox" value={fruit} bind:group={selectedFruits} />
            {fruit}
        </label>
    {/each}
    
    <p>Selected: {selectedFruits.join(", ") || "None"}</p>
    


  12. Radio Button Binding

  13. <script lang="ts">
        let selectedColor: string = $state("red");
    
        const colors: string[] = ["red", "green", "blue"];
    </script>
    
    {#each colors as color}
        <label>
            <input type="radio" value={color} bind:group={selectedColor} />
            {color}
        </label>
    {/each}
    
    <p>Selected color: {selectedColor}</p>
    


  14. Radio Button with Objects

  15. <script lang="ts">
        interface Plan {
            id: string;
            name: string;
            price: number;
        }
    
        const plans: Plan[] = [
            { id: "basic",      name: "Basic",      price: 9.99  },
            { id: "pro",        name: "Pro",        price: 19.99 },
            { id: "enterprise", name: "Enterprise", price: 49.99 }
        ];
    
        let selectedPlan: Plan = $state(plans[0]);
    </script>
    
    {#each plans as plan}
        <label>
            <input type="radio" value={plan} bind:group={selectedPlan} />
            {plan.name} - ${plan.price}/month
        </label>
    {/each}
    
    <p>Selected: {selectedPlan.name} at ${selectedPlan.price}</p>
    


  16. Select Dropdown Binding

  17. <script lang="ts">
        const selectedCountry: string = $state("");
    
        const countries: string[] = ["USA", "Canada", "UK", "Germany", "Japan"];
    </script>
    
    <select bind:value={selectedCountry}>
        <option value="">Select a country</option>
        {#each countries as country}
            <option value={country}>{country}</option>
        {/each}
    </select>
    
    <p>Selected: {selectedCountry || "None"}</p>
    


  18. Select with Objects

  19. <script lang="ts">
        interface Country {
            code: string;
            name: string;
            population: number;
        }
    
        const countries: Country[] = [
            { code: "US", name: "United States", population: 331000000 },
            { code: "JP", name: "Japan",         population: 125800000 },
            { code: "DE", name: "Germany",       population: 83200000 }
        ];
    
        const selectedCountry: Country | undefined = $state(undefined);
    </script>
    
    <select bind:value={selectedCountry}>
        <option value={undefined}>Select a country</option>
        {#each countries as country}
            <option value={country}>{country.name}</option>
        {/each}
    </select>
    
    {#if selectedCountry}
        <p>{selectedCountry.name}: {selectedCountry.population.toLocaleString()} people</p>
    {/if}
    


  20. Multiple Select Binding

  21. <script lang="ts">
        const selectedSkills: string[] = $state([]);
    
        const skills: string[] = ["JavaScript", "TypeScript", "Python", "Rust", "Go"];
    </script>
    
    <select multiple bind:value={selectedSkills}>
        {#each skills as skill}
            <option value={skill}>{skill}</option>
        {/each}
    </select>
    
    <p>Selected: {selectedSkills.join(", ") || "None"}</p>
    


  22. Textarea Binding

  23. <script lang="ts">
        const bio: string = $state("");
    </script>
    
    <textarea bind:value={bio} rows="4" cols="50"></textarea>
    
    <p>Character count: {bio.length}</p>
    <p>Preview:</p>
    <pre>{bio}</pre>
    


  24. Contenteditable Binding

  25. <script lang="ts">
        let plainText: string = $state("Edit this text");
        let richText: string = $state("<b>Bold</b> and <i>italic</i>");
    </script>
    
    <!-- Plain text binding -->
    <div contenteditable="true" bind:textContent={plainText}></div>
    <p>Plain: {plainText}</p>
    
    <!-- HTML binding -->
    <div contenteditable="true" bind:innerHTML={richText}></div>
    <p>HTML: {richText}</p>
    


  26. Element Reference Binding (bind:this)

  27. <script lang="ts">
        import { onMount } from "svelte";
    
        let inputElement: HTMLInputElement;
        let canvasElement: HTMLCanvasElement;
    
        onMount(() => {
            // Focus the input on mount
            inputElement.focus();
    
            // Draw on canvas
            const ctx = canvasElement.getContext("2d");
            if (ctx) {
                ctx.fillStyle = "blue";
                ctx.fillRect(10, 10, 100, 100);
            }
        });
    </script>
    
    <input bind:this={inputElement} placeholder="Auto-focused" />
    <canvas bind:this={canvasElement} width="200" height="150"></canvas>
    


  28. Element Reference with Svelte 5

  29. <script lang="ts">
        let inputElement: HTMLInputElement | undefined = $state(undefined);
    
        $effect(() => {
            if (inputElement) {
                inputElement.focus();
            }
        });
    </script>
    
    <input bind:this={inputElement} placeholder="Auto-focused" />
    


  30. Dimension Bindings

  31. <script lang="ts">
        let clientWidth: number = $state(0);
        let clientHeight: number = $state(0);
        let offsetWidth: number = $state(0);
        let offsetHeight: number = $state(0);
    </script>
    
    <div
        bind:clientWidth
        bind:clientHeight
        bind:offsetWidth
        bind:offsetHeight
        style="padding: 20px; border: 5px solid black;"
    >
        <p>Resize the window to see changes</p>
    </div>
    
    <p>Client: {clientWidth} x {clientHeight}</p>
    <p>Offset: {offsetWidth} x {offsetHeight}</p>
    


  32. Media Element Bindings

  33. <script lang="ts">
        let videoElement: HTMLVideoElement;
    
        // Readonly bindings
        let duration: number = $state(0);
        let buffered: TimeRanges;
        let seekable: TimeRanges;
        let played: TimeRanges;
        let seeking: boolean = $state(false);
        let ended: boolean = $state(false);
        let readyState: number = $state(0);
        let videoWidth: number = $state(0);
        let videoHeight: number = $state(0);
    
        // Two-way bindings
        let currentTime: number = $state(0);
        let playbackRate: number = $state(1);
        let paused: boolean = $state(true);
        let volume: number = $state(1);
        let muted: boolean = $state(false);
    
        function formatTime(seconds: number): string {
            const mins = Math.floor(seconds / 60);
            const secs = Math.floor(seconds % 60);
            return `${mins}:${secs.toString().padStart(2, "0")}`;
        }
    </script>
    
    <video
        bind:this={videoElement}
        bind:duration
        bind:currentTime
        bind:paused
        bind:volume
        bind:muted
        bind:playbackRate
        bind:seeking
        bind:ended
        src="video.mp4"
        width="400"
    >
        <track kind="captions" />
    </video>
    
    <div>
        <button onclick={() => paused = !paused}>
            {paused ? "Play" : "Pause"}
        </button>
        <span>{formatTime(currentTime)} / {formatTime(duration)}</span>
    </div>
    
    <div>
        <label>
            Volume:
            <input type="range" bind:value={volume} min="0" max="1" step="0.1" />
        </label>
        <label>
            <input type="checkbox" bind:checked={muted} />
            Muted
        </label>
    </div>
    
    <div>
        <label>
            Speed:
            <select bind:value={playbackRate}>
                <option value={0.5}>0.5x</option>
                <option value={1}>1x</option>
                <option value={1.5}>1.5x</option>
                <option value={2}>2x</option>
            </select>
        </label>
    </div>
    


  34. Window Bindings

  35. <script lang="ts">
        // Readonly
        let innerWidth: number = $state(0);
        let innerHeight: number = $state(0);
        let outerWidth: number = $state(0);
        let outerHeight: number = $state(0);
        let online: boolean = $state(true);
    
        // Two-way
        let scrollX: number = $state(0);
        let scrollY: number = $state(0);
    </script>
    
    <svelte:window
        bind:innerWidth
        bind:innerHeight
        bind:outerWidth
        bind:outerHeight
        bind:scrollX
        bind:scrollY
        bind:online
    />
    
    <div style="position: fixed; top: 10px; right: 10px; background: white; padding: 10px;">
        <p>Window: {innerWidth} x {innerHeight}</p>
        <p>Scroll: {scrollX}, {scrollY}</p>
        <p>Online: {online ? "Yes" : "No"}</p>
    </div>
    
    <button onclick={() => scrollY = 0}>Scroll to top</button>
    


  36. Document Bindings

  37. <script lang="ts">
        let activeElement: Element | null = $state(null);
        let fullscreenElement: Element | null = $state(null);
        let pointerLockElement: Element | null = $state(null);
        let visibilityState: DocumentVisibilityState = $state("visible");
    </script>
    
    <svelte:document
        bind:activeElement
        bind:fullscreenElement
        bind:pointerLockElement
        bind:visibilityState
    />
    
    <p>Active element: {activeElement?.tagName || "None"}</p>
    <p>Visibility: {visibilityState}</p>
    
    <input placeholder="Focus me" />
    <button>Or focus me</button>
    


  38. Component Bindings

  39. <!-- Counter.svelte (Svelte 4) -->
    <script lang="ts">
        export let count: number = 0;
    
        export function reset(): void {
            count = 0;
        }
    </script>
    
    <button on:click={() => count++}>{count}</button>
    
    <!-- Parent.svelte (Svelte 4) -->
    <script lang="ts">
        import Counter from "./Counter.svelte";
    
        let counterValue: number;
        let counterComponent: Counter;
    </script>
    
    <Counter bind:count={counterValue} bind:this={counterComponent} />
    
    <p>Counter value: {counterValue}</p>
    <button on:click={() => counterComponent.reset()}>Reset</button>
    


  40. Component Bindings (Svelte 5)

  41. <!-- Counter.svelte (Svelte 5) -->
    <script lang="ts">
        interface Props {
            count?: number;
        }
    
        let { count = $bindable(0) }: Props = $props();
    </script>
    
    <button onclick={() => count++}>{count}</button>
    
    <!-- Parent.svelte (Svelte 5) -->
    <script lang="ts">
        import Counter from "./Counter.svelte";
    
        let counterValue: number = $state(0);
    </script>
    
    <Counter bind:count={counterValue} />
    
    <p>Counter value: {counterValue}</p>
    <button onclick={() => counterValue = 0}>Reset</button>
    


  42. Binding Shorthand

  43. <script lang="ts">
        let value: string = $state("");
        let checked: boolean = $state(false);
        let clientWidth: number = $state(0);
        let innerWidth: number = $state(0);
    </script>
    
    <!-- These are equivalent: -->
    <input bind:value={value} />
    <input bind:value />
    
    <input type="checkbox" bind:checked={checked} />
    <input type="checkbox" bind:checked />
    
    <div bind:clientWidth={clientWidth}>...</div>
    <div bind:clientWidth>...</div>
    
    <svelte:window bind:innerWidth={innerWidth} />
    <svelte:window bind:innerWidth />
    


  44. Binding Summary

  45. Element Binding Type
    Text input bind:value string
    Number/Range input bind:value number
    Checkbox bind:checked boolean
    Checkbox group bind:group Array
    Radio group bind:group Single value
    Select bind:value Any
    Multi-select bind:value Array
    Textarea bind:value string
    Contenteditable bind:textContent, bind:innerHTML string
    Any element bind:this HTMLElement
    Any element bind:clientWidth, etc. number (readonly)
    Audio/Video bind:currentTime, bind:paused, etc. Various
    <svelte:window> bind:innerWidth, bind:scrollY, etc. number
    Component (Svelte 5) bind:prop with $bindable Any



Svelte Classes

  1. What Are Class Directives?



  2. Basic Class Attribute

  3. <script lang="ts">
        let className: string = "button primary";
    </script>
    
    <button class="button">Static class</button>
    <button class={className}>Dynamic class</button>
    


  4. Conditional Classes with Ternary

  5. <script lang="ts">
        let isActive: boolean = $state(false);
        let isDisabled: boolean = $state(false);
    </script>
    
    <button class={isActive ? "active" : ""}>
        Toggle
    </button>
    
    <button class={isActive ? "btn active" : "btn"}>
        With base class
    </button>
    
    <button class="btn {isActive ? 'active' : ''} {isDisabled ? 'disabled' : ''}">
        Multiple conditions
    </button>
    


  6. The class: Directive

  7. <script lang="ts">
        let isActive: boolean = $state(false);
    </script>
    
    <button class:active={isActive}>
        Toggle
    </button>
    
    <!-- The class "active" is added when isActive is true -->
    
    <style>
        .active {
            background-color: #4CAF50;
            color: white;
        }
    </style>
    


  8. Shorthand class: Directive

  9. <script lang="ts">
        let active: boolean = $state(false);
        let disabled: boolean = $state(false);
        let hidden: boolean = $state(false);
    </script>
    
    <!-- These are equivalent: -->
    <button class:active={active}>Long form</button>
    <button class:active>Shorthand</button>
    
    <!-- Multiple shorthand classes -->
    <button class:active class:disabled class:hidden>
        Multiple
    </button>
    


  10. Multiple Class Directives

  11. <script lang="ts">
        let isActive: boolean = $state(true);
        let isLarge: boolean = $state(false);
        let isPrimary: boolean = $state(true);
        let isDisabled: boolean = $state(false);
    </script>
    
    <button
        class="btn"
        class:active={isActive}
        class:large={isLarge}
        class:primary={isPrimary}
        class:disabled={isDisabled}
    >
        Styled Button
    </button>
    
    <style>
        .btn {
            padding: 10px 20px;
            border: none;
            cursor: pointer;
        }
        .active { border: 2px solid blue; }
        .large { font-size: 1.5rem; padding: 15px 30px; }
        .primary { background-color: #007bff; color: white; }
        .disabled { opacity: 0.5; cursor: not-allowed; }
    </style>
    


  12. Combining class and class:

  13. <script lang="ts">
        let variant: string = $state("primary");
        let isLoading: boolean = $state(false);
        let isFullWidth: boolean = $state(false);
    </script>
    
    <button
        class="btn btn-{variant}"
        class:loading={isLoading}
        class:full-width={isFullWidth}
    >
        {isLoading ? "Loading..." : "Submit"}
    </button>
    
    <style>
        .btn { padding: 10px 20px; }
        .btn-primary { background: blue; color: white; }
        .btn-secondary { background: gray; color: white; }
        .btn-danger { background: red; color: white; }
        .loading { opacity: 0.7; pointer-events: none; }
        .full-width { width: 100%; }
    </style>
    


  14. Class Directive with Expressions

  15. <script lang="ts">
        let count: number = $state(0);
        let status: string = $state("pending");
        let items: string[] = $state(["a", "b"]);
    </script>
    
    <!-- Comparison expressions -->
    <div class:warning={count > 5}>Count warning</div>
    <div class:danger={count > 10}>Count danger</div>
    
    <!-- Equality checks -->
    <div class:success={status === "completed"}>Status</div>
    <div class:pending={status === "pending"}>Status</div>
    
    <!-- Array/Object checks -->
    <div class:empty={items.length === 0}>Items</div>
    <div class:has-items={items.length > 0}>Items</div>
    
    <!-- Logical expressions -->
    <div class:special={count > 5 && status === "completed"}>
        Special
    </div>
    


  16. Dynamic Class Names

  17. <script lang="ts">
        let size: "sm" | "md" | "lg" = $state("md");
        let color: "red" | "green" | "blue" = $state("blue");
        let theme: "light" | "dark" = $state("light");
    </script>
    
    <button class="btn btn-{size} btn-{color}">
        Dynamic Size & Color
    </button>
    
    <div class="container theme-{theme}">
        Themed Container
    </div>
    
    <!-- With conditional fallback -->
    <div class="icon icon-{status || 'default'}">
        Icon
    </div>
    
    <style>
        .btn-sm { padding: 5px 10px; font-size: 0.8rem; }
        .btn-md { padding: 10px 20px; font-size: 1rem; }
        .btn-lg { padding: 15px 30px; font-size: 1.2rem; }
        .btn-red { background: red; }
        .btn-green { background: green; }
        .btn-blue { background: blue; }
        .theme-light { background: white; color: black; }
        .theme-dark { background: #333; color: white; }
    </style>
    


  18. Class Object Pattern

  19. <script lang="ts">
        // Utility function to build class string from object
        function classNames(classes: Record<string, boolean>): string {
            return Object.entries(classes)
                .filter(([, value]) => value)
                .map(([key]) => key)
                .join(" ");
        }
    
        let isActive: boolean = $state(true);
        let isDisabled: boolean = $state(false);
        let size: string = $state("large");
    
        let buttonClasses = $derived(classNames({
            "btn": true,
            "btn-active": isActive,
            "btn-disabled": isDisabled,
            "btn-large": size === "large",
            "btn-small": size === "small"
        }));
    </script>
    
    <button class={buttonClasses}>
        Dynamic Classes
    </button>
    


  20. Using clsx or classnames Library

  21. npm install clsx
    
    <script lang="ts">
        import clsx from "clsx";
    
        let isActive: boolean = $state(false);
        let isDisabled: boolean = $state(false);
        let variant: "primary" | "secondary" = $state("primary");
    
        let classes = $derived(clsx(
            "btn",
            `btn-${variant}`,
            {
                "active": isActive,
                "disabled": isDisabled
            }
        ));
    </script>
    
    <button class={classes}>
        Using clsx
    </button>
    


  22. Classes in Loops

  23. <script lang="ts">
        interface Item {
            id: number;
            name: string;
            completed: boolean;
            priority: "low" | "medium" | "high";
        }
    
        let items: Item[] = $state([
            { id: 1, name: "Task 1", completed: false, priority: "high" },
            { id: 2, name: "Task 2", completed: true, priority: "low" },
            { id: 3, name: "Task 3", completed: false, priority: "medium" }
        ]);
    
        let selectedId: number | null = $state(null);
    </script>
    
    <ul>
        {#each items as item (item.id)}
            <li
                class="item priority-{item.priority}"
                class:completed={item.completed}
                class:selected={selectedId === item.id}
                onclick={() => selectedId = item.id}
            >
                {item.name}
            </li>
        {/each}
    </ul>
    
    <style>
        .item { padding: 10px; cursor: pointer; }
        .completed { text-decoration: line-through; opacity: 0.6; }
        .selected { background-color: #e0e0e0; }
        .priority-low { border-left: 3px solid green; }
        .priority-medium { border-left: 3px solid orange; }
        .priority-high { border-left: 3px solid red; }
    </style>
    


  24. Global Classes vs Scoped Classes

  25. <script lang="ts">
        let isDark: boolean = $state(false);
    </script>
    
    <div class="container" class:dark={isDark}>
        <p>This is styled locally.</p>
    </div>
    
    <style>
        /* Scoped to this component */
        .container {
            padding: 20px;
            background: white;
        }
    
        .dark {
            background: #333;
            color: white;
        }
    
        /* Global style - affects all elements */
        :global(.global-class) {
            font-weight: bold;
        }
    
        /* Global modifier within scoped context */
        .container :global(.highlight) {
            background-color: yellow;
        }
    </style>
    


  26. Passing Classes to Child Components

  27. <!-- Button.svelte -->
    <script lang="ts">
        interface Props {
            variant?: "primary" | "secondary";
            class?: string;
        }
    
        let { variant = "primary", class: className = "" }: Props = $props();
    </script>
    
    <button class="btn btn-{variant} {className}">
        <slot />
    </button>
    
    <style>
        .btn { padding: 10px 20px; border: none; cursor: pointer; }
        .btn-primary { background: blue; color: white; }
        .btn-secondary { background: gray; color: white; }
    </style>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Button from "./Button.svelte";
    </script>
    
    <Button class="my-custom-class">Click me</Button>
    <Button variant="secondary" class="full-width">Submit</Button>
    
    <style>
        :global(.my-custom-class) {
            margin: 10px;
        }
        :global(.full-width) {
            width: 100%;
        }
    </style>
    


  28. Using $$restProps for Class Forwarding (Svelte 4)

  29. <!-- Input.svelte (Svelte 4) -->
    <script lang="ts">
        export let label: string = "";
    </script>
    
    <label>
        {label}
        <input {...$$restProps} />
    </label>
    
    <!-- Usage -->
    <Input label="Email" class="form-input" type="email" placeholder="Enter email" />
    


  30. Using Rest Props for Class Forwarding (Svelte 5)

  31. <!-- Input.svelte (Svelte 5) -->
    <script lang="ts">
        import type { HTMLInputAttributes } from "svelte/elements";
    
        interface Props extends HTMLInputAttributes {
            label?: string;
        }
    
        let { label = "", ...rest }: Props = $props();
    </script>
    
    <label>
        {label}
        <input {...rest} />
    </label>
    
    <!-- Usage -->
    <Input label="Email" class="form-input" type="email" placeholder="Enter email" />
    


  32. Animated Class Transitions

  33. <script lang="ts">
        let expanded: boolean = $state(false);
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => expanded = !expanded}>
        Toggle Expand
    </button>
    
    <div class="box" class:expanded>
        <p>Content here</p>
    </div>
    
    <button onclick={() => visible = !visible}>
        Toggle Visibility
    </button>
    
    <div class="fade-box" class:visible>
        <p>Fading content</p>
    </div>
    
    <style>
        .box {
            height: 50px;
            overflow: hidden;
            background: #f0f0f0;
            transition: height 0.3s ease;
        }
    
        .box.expanded {
            height: 200px;
        }
    
        .fade-box {
            opacity: 0;
            transform: translateY(-10px);
            transition: opacity 0.3s ease, transform 0.3s ease;
        }
    
        .fade-box.visible {
            opacity: 1;
            transform: translateY(0);
        }
    </style>
    


  34. Class Directive Summary

  35. Syntax Description Example
    class="name" Static class <div class="container">
    class={expr} Dynamic class string <div class={className}>
    class="a {b}" Mixed static + dynamic <div class="btn {variant}">
    class:name={cond} Conditional class <div class:active={isActive}>
    class:name Shorthand (name === var) <div class:active>
    Multiple class: Multiple conditionals <div class:a class:b class:c>
    :global() Escape scoping :global(.class) { }



Svelte Actions

  1. What Are Actions?



  2. Basic Action Syntax

  3. <script lang="ts">
        function greet(node: HTMLElement): void {
            console.log("Element mounted:", node);
        }
    </script>
    
    <div use:greet>
        Hello, World!
    </div>
    


  4. Action with Cleanup

  5. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        function logger(node: HTMLElement): ActionReturn {
            console.log("Mounted:", node);
    
            return {
                destroy() {
                    console.log("Destroyed:", node);
                }
            };
        }
    </script>
    
    <div use:logger>
        Watch the console
    </div>
    


  6. Action with Parameters

  7. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface TooltipParams {
            text: string;
            position?: "top" | "bottom" | "left" | "right";
        }
    
        function tooltip(node: HTMLElement, params: TooltipParams): ActionReturn<TooltipParams> {
            const { text, position = "top" } = params;
    
            node.setAttribute("title", text);
            node.setAttribute("data-position", position);
    
            console.log(`Tooltip: "${text}" at ${position}`);
    
            return {
                destroy() {
                    node.removeAttribute("title");
                    node.removeAttribute("data-position");
                }
            };
        }
    </script>
    
    <button use:tooltip={{ text: "Click to submit", position: "bottom" }}>
        Submit
    </button>
    
    <button use:tooltip={{ text: "Cancel operation" }}>
        Cancel
    </button>
    


  8. Action with Update

  9. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface ColorParams {
            color: string;
        }
    
        function backgroundColor(node: HTMLElement, params: ColorParams): ActionReturn<ColorParams> {
            node.style.backgroundColor = params.color;
    
            return {
                update(newParams: ColorParams) {
                    node.style.backgroundColor = newParams.color;
                },
                destroy() {
                    node.style.backgroundColor = "";
                }
            };
        }
    
        const color: string = $state("#ff0000");
    </script>
    
    <input type="color" bind:value={color} />
    
    <div use:backgroundColor={{ color }} style="padding: 20px;">
        Dynamic background color
    </div>
    


  10. Typing Actions with ActionReturn

  11. import type { ActionReturn } from "svelte/action";
    
    // Action without parameters
    function simpleAction(node: HTMLElement): ActionReturn {
        return {
            destroy() {}
        };
    }
    
    // Action with required parameters
    function paramAction(node: HTMLElement, params: string): ActionReturn<string> {
        return {
            update(newParams: string) {},
            destroy() {}
        };
    }
    
    // Action with optional parameters
    function optionalAction(node: HTMLElement, params?: number): ActionReturn<number | undefined> {
        return {
            update(newParams?: number) {},
            destroy() {}
        };
    }
    


  12. Using the Action Type

  13. import type { Action } from "svelte/action";
    
    interface FocusTrapParams {
        enabled?: boolean;
    }
    
    const focusTrap: Action<HTMLElement, FocusTrapParams> = (node, params) => {
        // Implementation
        return {
            update(newParams) {
                // Handle updates
            },
            destroy() {
                // Cleanup
            }
        };
    };
    


  14. Click Outside Action

  15. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface ClickOutsideParams {
            onClickOutside: () => void;
        }
    
        function clickOutside(node: HTMLElement, params: ClickOutsideParams): ActionReturn<ClickOutsideParams> {
            let { onClickOutside } = params;
    
            function handleClick(event: MouseEvent): void {
                if (!node.contains(event.target as Node)) {
                    onClickOutside();
                }
            }
    
            document.addEventListener("click", handleClick, true);
    
            return {
                update(newParams: ClickOutsideParams) {
                    onClickOutside = newParams.onClickOutside;
                },
                destroy() {
                    document.removeEventListener("click", handleClick, true);
                }
            };
        }
    
        let showDropdown: boolean = $state(false);
    </script>
    
    <div class="dropdown-container">
        <button onclick={() => showDropdown = !showDropdown}>
            Toggle Dropdown
        </button>
    
        {#if showDropdown}
            <div
                class="dropdown"
                use:clickOutside={{ onClickOutside: () => showDropdown = false }}
            >
                <p>Dropdown content</p>
                <p>Click outside to close</p>
            </div>
        {/if}
    </div>
    
    <style>
        .dropdown-container { position: relative; }
        .dropdown {
            position: absolute;
            top: 100%;
            left: 0;
            background: white;
            border: 1px solid #ccc;
            padding: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
    </style>
    


  16. Auto Focus Action

  17. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface FocusParams {
            delay?: number;
            select?: boolean;
        }
    
        function autoFocus(node: HTMLElement, params: FocusParams = {}): ActionReturn<FocusParams> {
            const { delay = 0, select = false } = params;
    
            const timeoutId = setTimeout(() => {
                node.focus();
                if (select && node instanceof HTMLInputElement) {
                    node.select();
                }
            }, delay);
    
            return {
                destroy() {
                    clearTimeout(timeoutId);
                }
            };
        }
    
        let showModal: boolean = $state(false);
    </script>
    
    <button onclick={() => showModal = true}>Open Modal</button>
    
    {#if showModal}
        <div class="modal">
            <input
                use:autoFocus={{ select: true }}
                value="Selected text"
            />
            <button onclick={() => showModal = false}>Close</button>
        </div>
    {/if}
    


  18. Longpress Action

  19. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface LongpressParams {
            duration?: number;
            onLongpress: () => void;
        }
    
        function longpress(node: HTMLElement, params: LongpressParams): ActionReturn<LongpressParams> {
            let { duration = 500, onLongpress } = params;
            let timeoutId: ReturnType<typeof setTimeout>;
    
            function handleMouseDown(): void {
                timeoutId = setTimeout(() => {
                    onLongpress();
                }, duration);
            }
    
            function handleMouseUp(): void {
                clearTimeout(timeoutId);
            }
    
            node.addEventListener("mousedown", handleMouseDown);
            node.addEventListener("mouseup", handleMouseUp);
            node.addEventListener("mouseleave", handleMouseUp);
    
            return {
                update(newParams: LongpressParams) {
                    duration = newParams.duration ?? 500;
                    onLongpress = newParams.onLongpress;
                },
                destroy() {
                    clearTimeout(timeoutId);
                    node.removeEventListener("mousedown", handleMouseDown);
                    node.removeEventListener("mouseup", handleMouseUp);
                    node.removeEventListener("mouseleave", handleMouseUp);
                }
            };
        }
    
        let message: string = $state("Press and hold the button");
    </script>
    
    <button use:longpress={{ duration: 1000, onLongpress: () => message = "Longpress detected!" }}>
        Hold me
    </button>
    
    <p>{message}</p>
    


  20. Lazy Load Image Action

  21. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface LazyLoadParams {
            src: string;
            placeholder?: string;
        }
    
        function lazyLoad(node: HTMLImageElement, params: LazyLoadParams): ActionReturn<LazyLoadParams> {
            let { src, placeholder = "" } = params;
    
            // Set placeholder initially
            if (placeholder) {
                node.src = placeholder;
            }
    
            const observer = new IntersectionObserver(
                (entries) => {
                    entries.forEach((entry) => {
                        if (entry.isIntersecting) {
                            node.src = src;
                            observer.unobserve(node);
                        }
                    });
                },
                { rootMargin: "50px" }
            );
    
            observer.observe(node);
    
            return {
                update(newParams: LazyLoadParams) {
                    src = newParams.src;
                    if (newParams.placeholder) {
                        placeholder = newParams.placeholder;
                    }
                },
                destroy() {
                    observer.disconnect();
                }
            };
        }
    
        const images: string[] = [
            "https://picsum.photos/400/300?1",
            "https://picsum.photos/400/300?2",
            "https://picsum.photos/400/300?3"
        ];
    </script>
    
    {#each images as src, i}
        <img
            use:lazyLoad={{ src, placeholder: "placeholder.jpg" }}
            alt="Lazy loaded image {i + 1}"
            width="400"
            height="300"
        />
    {/each}
    


  22. Portal Action

  23. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface PortalParams {
            target?: string | HTMLElement;
        }
    
        function portal(node: HTMLElement, params: PortalParams = {}): ActionReturn<PortalParams> {
            let targetEl: HTMLElement;
    
            function update(newParams: PortalParams): void {
                const { target = "body" } = newParams;
    
                if (typeof target === "string") {
                    targetEl = document.querySelector(target) ?? document.body;
                } else {
                    targetEl = target;
                }
    
                targetEl.appendChild(node);
            }
    
            update(params);
    
            return {
                update,
                destroy() {
                    node.remove();
                }
            };
        }
    
        let showModal: boolean = $state(false);
    </script>
    
    <button onclick={() => showModal = true}>Open Modal</button>
    
    {#if showModal}
        <div class="modal-overlay" use:portal>
            <div class="modal-content">
                <h2>Modal Title</h2>
                <p>This is rendered at the end of body.</p>
                <button onclick={() => showModal = false}>Close</button>
            </div>
        </div>
    {/if}
    
    <style>
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .modal-content {
            background: white;
            padding: 20px;
            border-radius: 8px;
        }
    </style>
    


  24. Resize Observer Action

  25. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface ResizeParams {
            onResize: (width: number, height: number) => void;
        }
    
        function resize(node: HTMLElement, params: ResizeParams): ActionReturn<ResizeParams> {
            let { onResize } = params;
    
            const observer = new ResizeObserver((entries) => {
                for (const entry of entries) {
                    const { width, height } = entry.contentRect;
                    onResize(width, height);
                }
            });
    
            observer.observe(node);
    
            return {
                update(newParams: ResizeParams) {
                    onResize = newParams.onResize;
                },
                destroy() {
                    observer.disconnect();
                }
            };
        }
    
        let width: number = $state(0);
        let height: number = $state(0);
    </script>
    
    <div
        class="resizable"
        use:resize={{ onResize: (w, h) => { width = w; height = h; } }}
    >
        <p>Resize me!</p>
        <p>{width.toFixed(0)} x {height.toFixed(0)}</p>
    </div>
    
    <style>
        .resizable {
            resize: both;
            overflow: auto;
            border: 2px solid #333;
            padding: 20px;
            min-width: 100px;
            min-height: 100px;
        }
    </style>
    


  26. Intersection Observer Action

  27. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface InViewParams {
            onEnter?: () => void;
            onLeave?: () => void;
            threshold?: number;
            once?: boolean;
        }
    
        function inView(node: HTMLElement, params: InViewParams = {}): ActionReturn<InViewParams> {
            let { onEnter, onLeave, threshold = 0, once = false } = params;
    
            const observer = new IntersectionObserver(
                (entries) => {
                    entries.forEach((entry) => {
                        if (entry.isIntersecting) {
                            onEnter?.();
                            if (once) {
                                observer.unobserve(node);
                            }
                        } else {
                            onLeave?.();
                        }
                    });
                },
                { threshold }
            );
    
            observer.observe(node);
    
            return {
                update(newParams: InViewParams) {
                    onEnter = newParams.onEnter;
                    onLeave = newParams.onLeave;
                },
                destroy() {
                    observer.disconnect();
                }
            };
        }
    
        let isVisible: boolean = $state(false);
    </script>
    
    <div style="height: 150vh;">
        <p>Scroll down...</p>
    </div>
    
    <div
        class="observed"
        class:visible={isVisible}
        use:inView={{
            onEnter: () => isVisible = true,
            onLeave: () => isVisible = false,
            threshold: 0.5
        }}
    >
        I'm being observed!
    </div>
    
    <style>
        .observed {
            padding: 40px;
            background: #eee;
            transition: background 0.3s;
        }
        .observed.visible {
            background: #4CAF50;
            color: white;
        }
    </style>
    


  28. Third-Party Library Integration

  29. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
        // Assuming tippy.js is installed
        // import tippy, { type Instance, type Props } from "tippy.js";
    
        interface TippyParams {
            content: string;
            placement?: "top" | "bottom" | "left" | "right";
        }
    
        function tippyAction(node: HTMLElement, params: TippyParams): ActionReturn<TippyParams> {
            // const instance: Instance = tippy(node, {
            //     content: params.content,
            //     placement: params.placement ?? "top"
            // });
    
            // Simulated for demonstration
            node.title = params.content;
    
            return {
                update(newParams: TippyParams) {
                    // instance.setContent(newParams.content);
                    // instance.setProps({ placement: newParams.placement });
                    node.title = newParams.content;
                },
                destroy() {
                    // instance.destroy();
                    node.removeAttribute("title");
                }
            };
        }
    </script>
    
    <button use:tippyAction={{ content: "Hello Tooltip!", placement: "bottom" }}>
        Hover me
    </button>
    


  30. Copy to Clipboard Action

  31. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface CopyParams {
            text: string;
            onCopy?: () => void;
            onError?: (error: Error) => void;
        }
    
        function copy(node: HTMLElement, params: CopyParams): ActionReturn<CopyParams> {
            let { text, onCopy, onError } = params;
    
            async function handleClick(): Promise<void> {
                try {
                    await navigator.clipboard.writeText(text);
                    onCopy?.();
                } catch (err) {
                    onError?.(err as Error);
                }
            }
    
            node.addEventListener("click", handleClick);
    
            return {
                update(newParams: CopyParams) {
                    text = newParams.text;
                    onCopy = newParams.onCopy;
                    onError = newParams.onError;
                },
                destroy() {
                    node.removeEventListener("click", handleClick);
                }
            };
        }
    
        let copied: boolean = $state(false);
        const textToCopy: string = "Hello, World!";
    
        function handleCopy(): void {
            copied = true;
            setTimeout(() => copied = false, 2000);
        }
    </script>
    
    <code>{textToCopy}</code>
    
    <button use:copy={{ text: textToCopy, onCopy: handleCopy }}>
        {copied ? "Copied!" : "Copy"}
    </button>
    


  32. Shortcut / Hotkey Action

  33. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        interface ShortcutParams {
            key: string;
            ctrl?: boolean;
            shift?: boolean;
            alt?: boolean;
            onTrigger: () => void;
        }
    
        function shortcut(node: HTMLElement, params: ShortcutParams): ActionReturn<ShortcutParams> {
            let { key, ctrl = false, shift = false, alt = false, onTrigger } = params;
    
            function handleKeyDown(event: KeyboardEvent): void {
                if (
                    event.key.toLowerCase() === key.toLowerCase() &&
                    event.ctrlKey === ctrl &&
                    event.shiftKey === shift &&
                    event.altKey === alt
                ) {
                    event.preventDefault();
                    onTrigger();
                }
            }
    
            window.addEventListener("keydown", handleKeyDown);
    
            return {
                update(newParams: ShortcutParams) {
                    key = newParams.key;
                    ctrl = newParams.ctrl ?? false;
                    shift = newParams.shift ?? false;
                    alt = newParams.alt ?? false;
                    onTrigger = newParams.onTrigger;
                },
                destroy() {
                    window.removeEventListener("keydown", handleKeyDown);
                }
            };
        }
    
        let count: number = $state(0);
    </script>
    
    <div use:shortcut={{ key: "k", ctrl: true, onTrigger: () => count++ }}>
        <p>Press Ctrl+K to increment: {count}</p>
    </div>
    


  34. Multiple Actions on One Element

  35. <script lang="ts">
        import type { ActionReturn } from "svelte/action";
    
        function logMount(node: HTMLElement): ActionReturn {
            console.log("Mounted");
            return { destroy() { console.log("Destroyed"); } };
        }
    
        function addBorder(node: HTMLElement, color: string): ActionReturn<string> {
            node.style.border = `2px solid ${color}`;
            return {
                update(newColor: string) {
                    node.style.border = `2px solid ${newColor}`;
                },
                destroy() {
                    node.style.border = "";
                }
            };
        }
    
        function autoFocus(node: HTMLElement): ActionReturn {
            node.focus();
            return {};
        }
    </script>
    
    <input
        use:logMount
        use:addBorder={"blue"}
        use:autoFocus
        placeholder="Multiple actions"
    />
    


  36. Actions Summary

  37. Feature Description Syntax
    Basic action Function runs on mount use:action
    With parameter Pass data to action use:action={params}
    destroy Cleanup on unmount return { destroy() {} }
    update React to param changes return { update(p) {} }
    Type (function) Action return type ActionReturn<Params>
    Type (signature) Full action type Action<Element, Params>
    Multiple actions Combine on one element use:a use:b use:c



Svelte Transitions

  1. What Are Transitions?



  2. Built-in Transitions


  3. Transition Description
    fade Fades opacity in/out
    blur Fades with blur effect
    fly Flies in from a direction
    slide Slides vertically
    scale Scales size in/out
    draw Draws SVG paths
    crossfade Morphs between elements


  4. Fade Transition

  5. <script lang="ts">
        import { fade } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <p transition:fade>
            This fades in and out
        </p>
    {/if}
    
    <script lang="ts">
        import { fade } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    {#if visible}
        <p transition:fade={{ duration: 500, delay: 100 }}>
            Slower fade with delay
        </p>
    {/if}
    


  6. Fly Transition

  7. <script lang="ts">
        import { fly } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <!-- Fly from top -->
        <p transition:fly={{ y: -50, duration: 300 }}>
            Flies from top
        </p>
    
        <!-- Fly from left -->
        <p transition:fly={{ x: -200, duration: 300 }}>
            Flies from left
        </p>
    
        <!-- Fly with opacity control -->
        <p transition:fly={{ y: 100, opacity: 0.5, duration: 400 }}>
            Partial opacity fly
        </p>
    {/if}
    


  8. Slide Transition

  9. <script lang="ts">
        import { slide } from "svelte/transition";
    
        let expanded: boolean = $state(false);
    </script>
    
    <button onclick={() => expanded = !expanded}>
        {expanded ? "Collapse" : "Expand"}
    </button>
    
    {#if expanded}
        <div transition:slide={{ duration: 300 }}>
            <p>This content slides in and out.</p>
            <p>It animates the height property.</p>
            <p>Great for accordions and dropdowns.</p>
        </div>
    {/if}
    
    <script lang="ts">
        import { slide } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    {#if visible}
        <!-- Horizontal slide -->
        <div transition:slide={{ axis: "x", duration: 300 }}>
            Slides horizontally
        </div>
    {/if}
    


  10. Scale Transition

  11. <script lang="ts">
        import { scale } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <div transition:scale={{ duration: 300 }}>
            Scales in and out
        </div>
    
        <div transition:scale={{ start: 0.5, opacity: 0.5 }}>
            Starts at 50% size
        </div>
    {/if}
    


  12. Blur Transition

  13. <script lang="ts">
        import { blur } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <p transition:blur={{ amount: 10, duration: 400 }}>
            Blurs in and out
        </p>
    {/if}
    


  14. Draw Transition (SVG)

  15. <script lang="ts">
        import { draw } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <svg viewBox="0 0 100 100" width="200" height="200">
            <path
                transition:draw={{ duration: 1000 }}
                d="M10,50 Q50,10 90,50 Q50,90 10,50"
                fill="none"
                stroke="blue"
                stroke-width="2"
            />
        </svg>
    {/if}
    


  16. Separate In and Out Transitions

  17. <script lang="ts">
        import { fly, fade, scale } from "svelte/transition";
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <!-- Different transitions for in/out -->
        <p in:fly={{ y: -50 }} out:fade>
            Flies in, fades out
        </p>
    
        <p in:scale out:fly={{ x: 200 }}>
            Scales in, flies out to the right
        </p>
    {/if}
    


  18. Transition Parameters

  19. Parameter Type Description
    delay number Milliseconds before starting
    duration number Length in milliseconds
    easing function Easing function
    <script lang="ts">
        import { fade } from "svelte/transition";
        import { cubicOut, elasticOut, bounceOut } from "svelte/easing";
    
        let visible: boolean = $state(true);
    </script>
    
    {#if visible}
        <p transition:fade={{ duration: 300, delay: 100, easing: cubicOut }}>
            Custom easing
        </p>
    {/if}
    


  20. Easing Functions

  21. import {
        // Linear
        linear,
    
        // Sine
        sineIn, sineOut, sineInOut,
    
        // Quad (power of 2)
        quadIn, quadOut, quadInOut,
    
        // Cubic (power of 3)
        cubicIn, cubicOut, cubicInOut,
    
        // Quart (power of 4)
        quartIn, quartOut, quartInOut,
    
        // Quint (power of 5)
        quintIn, quintOut, quintInOut,
    
        // Expo (exponential)
        expoIn, expoOut, expoInOut,
    
        // Circ (circular)
        circIn, circOut, circInOut,
    
        // Back (overshoots)
        backIn, backOut, backInOut,
    
        // Elastic (springy)
        elasticIn, elasticOut, elasticInOut,
    
        // Bounce
        bounceIn, bounceOut, bounceInOut
    } from "svelte/easing";
    
    <script lang="ts">
        import { fly } from "svelte/transition";
        import { elasticOut, bounceOut } from "svelte/easing";
    
        let visible: boolean = $state(true);
    </script>
    
    {#if visible}
        <div transition:fly={{ y: -100, easing: elasticOut, duration: 800 }}>
            Elastic entrance!
        </div>
    
        <div transition:fly={{ y: 100, easing: bounceOut, duration: 600 }}>
            Bouncy entrance!
        </div>
    {/if}
    


  22. Transition Events

  23. <script lang="ts">
        import { fade } from "svelte/transition";
    
        let visible: boolean = $state(true);
        let status: string = $state("");
    
        function handleIntroStart(): void {
            status = "Intro started";
        }
    
        function handleIntroEnd(): void {
            status = "Intro ended";
        }
    
        function handleOutroStart(): void {
            status = "Outro started";
        }
    
        function handleOutroEnd(): void {
            status = "Outro ended";
        }
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    <p>Status: {status}</p>
    
    {#if visible}
        <div
            transition:fade={{ duration: 500 }}
            onintrostart={handleIntroStart}
            onintroend={handleIntroEnd}
            onoutrostart={handleOutroStart}
            onoutroend={handleOutroEnd}
        >
            Watch the status!
        </div>
    {/if}
    


  24. Local Transitions

  25. <script lang="ts">
        import { slide, fade } from "svelte/transition";
    
        let showList: boolean = $state(true);
        let items: string[] = $state(["Item 1", "Item 2", "Item 3"]);
    
        function addItem(): void {
            items = [...items, `Item ${items.length + 1}`];
        }
    
        function removeItem(index: number): void {
            items = items.filter((_, i) => i !== index);
        }
    </script>
    
    <button onclick={() => showList = !showList}>
        Toggle List
    </button>
    <button onclick={addItem}>Add Item</button>
    
    {#if showList}
        <ul transition:fade>
            {#each items as item, i (item)}
                <!-- |local prevents animation when parent toggles -->
                <li transition:slide|local>
                    {item}
                    <button onclick={() => removeItem(i)}>×</button>
                </li>
            {/each}
        </ul>
    {/if}
    


  26. Global Modifier

  27. <script lang="ts">
        import { fade } from "svelte/transition";
    </script>
    
    <!-- Plays transition on initial render -->
    <div transition:fade|global={{ duration: 1000 }}>
        Fades in on page load
    </div>
    


  28. Crossfade Transition

  29. <script lang="ts">
        import { crossfade } from "svelte/transition";
        import { quintOut } from "svelte/easing";
    
        const [send, receive] = crossfade({
            duration: 400,
            easing: quintOut,
            fallback(node) {
                // Fallback when no matching element exists
                return {
                    duration: 300,
                    css: (t: number) => `opacity: ${t}`
                };
            }
        });
    
        interface Todo {
            id: number;
            text: string;
            done: boolean;
        }
    
        let todos: Todo[] = $state([
            { id: 1, text: "Learn Svelte", done: false },
            { id: 2, text: "Build an app", done: false },
            { id: 3, text: "Deploy", done: true }
        ]);
    
        function toggle(id: number): void {
            todos = todos.map(t =>
                t.id === id ? { ...t, done: !t.done } : t
            );
        }
    
        let pending = $derived(todos.filter(t => !t.done));
        let completed = $derived(todos.filter(t => t.done));
    </script>
    
    <div class="columns">
        <div class="column">
            <h3>Pending</h3>
            {#each pending as todo (todo.id)}
                <div
                    class="todo"
                    in:receive={{ key: todo.id }}
                    out:send={{ key: todo.id }}
                    onclick={() => toggle(todo.id)}
                >
                    {todo.text}
                </div>
            {/each}
        </div>
    
        <div class="column">
            <h3>Completed</h3>
            {#each completed as todo (todo.id)}
                <div
                    class="todo done"
                    in:receive={{ key: todo.id }}
                    out:send={{ key: todo.id }}
                    onclick={() => toggle(todo.id)}
                >
                    {todo.text}
                </div>
            {/each}
        </div>
    </div>
    
    <style>
        .columns { display: flex; gap: 20px; }
        .column { flex: 1; }
        .todo { padding: 10px; margin: 5px 0; background: #eee; cursor: pointer; }
        .todo.done { background: #c8e6c9; }
    </style>
    


  30. Custom Transitions

  31. <script lang="ts">
        import type { TransitionConfig } from "svelte/transition";
    
        interface SpinParams {
            duration?: number;
            rotations?: number;
        }
    
        function spin(node: HTMLElement, params: SpinParams = {}): TransitionConfig {
            const { duration = 500, rotations = 1 } = params;
    
            return {
                duration,
                css: (t: number) => {
                    const rotation = t * rotations * 360;
                    const scale = t;
                    return `
                        transform: rotate(${rotation}deg) scale(${scale});
                        opacity: ${t};
                    `;
                }
            };
        }
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <div transition:spin={{ duration: 800, rotations: 2 }}>
            Spinning!
        </div>
    {/if}
    


  32. Custom Transition with tick

  33. <script lang="ts">
        import type { TransitionConfig } from "svelte/transition";
    
        interface TypewriterParams {
            speed?: number;
        }
    
        function typewriter(node: HTMLElement, params: TypewriterParams = {}): TransitionConfig {
            const { speed = 50 } = params;
            const text = node.textContent ?? "";
            const duration = text.length * speed;
    
            return {
                duration,
                tick: (t: number) => {
                    const length = Math.floor(text.length * t);
                    node.textContent = text.slice(0, length);
                }
            };
        }
    
        let visible: boolean = $state(true);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <p transition:typewriter={{ speed: 30 }}>
            This text appears character by character...
        </p>
    {/if}
    


  34. Transition Return Object

  35. interface TransitionConfig {
        delay?: number;           // Delay before starting
        duration?: number;        // Animation length in ms
        easing?: (t: number) => number;  // Easing function
        css?: (t: number, u: number) => string;  // CSS generator
        tick?: (t: number, u: number) => void;   // JS callback
    }
    
    // t: 0 to 1 (intro) or 1 to 0 (outro) - the progress
    // u: 1 - t (the inverse)
    
    <script lang="ts">
        import type { TransitionConfig } from "svelte/transition";
        import { cubicOut } from "svelte/easing";
    
        function customFade(node: HTMLElement): TransitionConfig {
            return {
                delay: 100,
                duration: 400,
                easing: cubicOut,
                css: (t: number, u: number) => `
                    opacity: ${t};
                    transform: translateY(${u * 20}px);
                `
            };
        }
    
        let visible: boolean = $state(true);
    </script>
    
    {#if visible}
        <div transition:customFade>
            Custom fade transition
        </div>
    {/if}
    


  36. Deferred Transitions

  37. <script lang="ts">
        import type { TransitionConfig } from "svelte/transition";
    
        function deferredSlide(node: HTMLElement): () => TransitionConfig {
            // Measure height when transition starts, not when component mounts
            return () => {
                const height = node.offsetHeight;
                return {
                    duration: 300,
                    css: (t: number) => `
                        height: ${t * height}px;
                        overflow: hidden;
                    `
                };
            };
        }
    
        let visible: boolean = $state(true);
    </script>
    
    {#if visible}
        <div transition:deferredSlide>
            Content with dynamic height
        </div>
    {/if}
    


  38. Staggered List Transitions

  39. <script lang="ts">
        import { fly } from "svelte/transition";
    
        let visible: boolean = $state(true);
    
        const items: string[] = [
            "First item",
            "Second item",
            "Third item",
            "Fourth item",
            "Fifth item"
        ];
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle List
    </button>
    
    {#if visible}
        <ul>
            {#each items as item, i}
                <li
                    in:fly={{ y: 20, delay: i * 100, duration: 300 }}
                    out:fly={{ y: -20, delay: (items.length - i - 1) * 50, duration: 200 }}
                >
                    {item}
                </li>
            {/each}
        </ul>
    {/if}
    


  40. Combining Transitions with CSS

  41. <script lang="ts">
        import { fade } from "svelte/transition";
    
        let visible: boolean = $state(true);
        let hovered: boolean = $state(false);
    </script>
    
    <button onclick={() => visible = !visible}>
        Toggle
    </button>
    
    {#if visible}
        <div
            class="box"
            class:hovered
            transition:fade={{ duration: 300 }}
            onmouseenter={() => hovered = true}
            onmouseleave={() => hovered = false}
        >
            Hover me!
        </div>
    {/if}
    
    <style>
        .box {
            padding: 20px;
            background: #3498db;
            color: white;
            transition: transform 0.2s, background 0.2s;
        }
        .box.hovered {
            transform: scale(1.05);
            background: #2980b9;
        }
    </style>
    


  42. Transition with {#key}

  43. <script lang="ts">
        import { fade, fly } from "svelte/transition";
    
        let count: number = $state(0);
    </script>
    
    <button onclick={() => count++}>
        Increment
    </button>
    
    {#key count}
        <p transition:fade={{ duration: 200 }}>
            Count: {count}
        </p>
    {/key}
    
    {#key count}
        <div in:fly={{ y: -20 }} out:fly={{ y: 20 }}>
            Value changed!
        </div>
    {/key}
    


  44. Transitions Summary

  45. Directive Description Example
    transition: In and out transition:fade
    in: Enter only in:fly={{ y: -50 }}
    out: Exit only out:fade
    |local Only direct block changes transition:slide|local
    |global Play on initial render transition:fade|global
    Events Lifecycle hooks onintroend={handler}
    Custom Return TransitionConfig css: (t) => ...



Svelte Advanced Reactivity


  1. Deep Reactivity with $state

  2. <script lang="ts">
        interface User {
            name: string;
            address: {
                city: string;
                country: string;
            };
            hobbies: string[];
        }
    
        let user: User = $state({
            name: "Alice",
            address: {
                city: "New York",
                country: "USA"
            },
            hobbies: ["reading", "coding"]
        });
    
        function updateCity(): void {
            // Deep mutation is reactive!
            user.address.city = "Los Angeles";
        }
    
        function addHobby(): void {
            // Array mutations are reactive!
            user.hobbies.push("gaming");
        }
    </script>
    
    <p>{user.name} lives in {user.address.city}</p>
    <p>Hobbies: {user.hobbies.join(", ")}</p>
    
    <button onclick={updateCity}>Move to LA</button>
    <button onclick={addHobby}>Add Hobby</button>
    


  3. Shallow Reactivity with $state.raw

  4. <script lang="ts">
        interface LargeDataset {
            id: number;
            values: number[];
        }
    
        // Only the reference is reactive, not nested properties
        let data: LargeDataset = $state.raw({
            id: 1,
            values: [1, 2, 3, 4, 5]
        });
    
        function updateData(): void {
            // This does NOT trigger reactivity
            data.values.push(6);
    
            // This DOES trigger reactivity (replacing the whole object)
            data = { ...data, values: [...data.values, 7] };
        }
    
        function replaceData(): void {
            // Reassignment triggers update
            data = {
                id: 2,
                values: [10, 20, 30]
            };
        }
    </script>
    
    <p>ID: {data.id}</p>
    <p>Values: {data.values.join(", ")}</p>
    
    <button onclick={updateData}>Update</button>
    <button onclick={replaceData}>Replace</button>
    


  5. Getting Plain Values with $state.snapshot

  6. <script lang="ts">
        interface FormData {
            name: string;
            email: string;
        }
    
        let form: FormData = $state({
            name: "",
            email: ""
        });
    
        function handleSubmit(): void {
            // Get a plain object snapshot for API calls
            const snapshot = $state.snapshot(form);
    
            console.log("Submitting:", snapshot);
            console.log("Is proxy:", form !== snapshot); // true
    
            // Send to API
            fetch("/api/submit", {
                method: "POST",
                body: JSON.stringify(snapshot)
            });
        }
    
        function logState(): void {
            // Reactive proxy - may show Proxy in console
            console.log("Reactive:", form);
    
            // Plain object - cleaner for debugging
            console.log("Snapshot:", $state.snapshot(form));
        }
    </script>
    
    <input bind:value={form.name} placeholder="Name" />
    <input bind:value={form.email} placeholder="Email" />
    
    <button onclick={handleSubmit}>Submit</button>
    <button onclick={logState}>Log State</button>
    


  7. Computed Values with $derived

  8. <script lang="ts">
        let firstName: string = $state("John");
        let lastName: string = $state("Doe");
        let age: number = $state(25);
    
        // Simple derived value
        let fullName: string = $derived(firstName + " " + lastName);
    
        // Derived with transformation
        let upperName: string = $derived(fullName.toUpperCase());
    
        // Derived boolean
        let isAdult: boolean = $derived(age >= 18);
    
        // Chained derivations
        let greeting: string = $derived(
            `Hello, ${fullName}! You are ${isAdult ? "an adult" : "a minor"}.`
        );
    </script>
    
    <input bind:value={firstName} placeholder="First name" />
    <input bind:value={lastName} placeholder="Last name" />
    <input type="number" bind:value={age} />
    
    <p>Full name: {fullName}</p>
    <p>Upper: {upperName}</p>
    <p>{greeting}</p>
    


  9. Complex Derivations with $derived.by

  10. <script lang="ts">
        interface Product {
            name: string;
            price: number;
            quantity: number;
        }
    
        let products: Product[] = $state([
            { name: "Apple", price: 1.5, quantity: 3 },
            { name: "Banana", price: 0.75, quantity: 5 },
            { name: "Orange", price: 2.0, quantity: 2 }
        ]);
    
        let taxRate: number = $state(0.08);
    
        // Complex derivation with multiple statements
        let orderSummary = $derived.by(() => {
            const subtotal = products.reduce(
                (sum, p) => sum + p.price * p.quantity,
                0
            );
            const tax = subtotal * taxRate;
            const total = subtotal + tax;
            const itemCount = products.reduce((sum, p) => sum + p.quantity, 0);
    
            return {
                subtotal: subtotal.toFixed(2),
                tax: tax.toFixed(2),
                total: total.toFixed(2),
                itemCount
            };
        });
    
        // Derived with conditional logic
        let recommendation = $derived.by(() => {
            const total = parseFloat(orderSummary.total);
    
            if (total > 50) {
                return "You qualify for free shipping!";
            } else if (total > 25) {
                return `Add $${(50 - total).toFixed(2)} more for free shipping.`;
            } else {
                return "Keep shopping for great deals!";
            }
        });
    </script>
    
    <ul>
        {#each products as product}
            <li>{product.name}: ${product.price} × {product.quantity}</li>
        {/each}
    </ul>
    
    <p>Items: {orderSummary.itemCount}</p>
    <p>Subtotal: ${orderSummary.subtotal}</p>
    <p>Tax: ${orderSummary.tax}</p>
    <p>Total: ${orderSummary.total}</p>
    <p>{recommendation}</p>
    


  11. Side Effects with $effect

  12. <script lang="ts">
        let count: number = $state(0);
        let name: string = $state("World");
    
        // Runs when count changes
        $effect(() => {
            console.log("Count is now:", count);
        });
    
        // Runs when name changes
        $effect(() => {
            document.title = `Hello, ${name}!`;
        });
    
        // Runs when either changes
        $effect(() => {
            console.log(`${name} has count ${count}`);
        });
    </script>
    
    <input bind:value={name} />
    <button onclick={() => count++}>Count: {count}</button>
    


  13. Effect Cleanup

  14. <script lang="ts">
        let interval: number = $state(1000);
        let count: number = $state(0);
    
        $effect(() => {
            // Set up interval with current value
            const id = setInterval(() => {
                count++;
            }, interval);
    
            console.log(`Started interval: ${interval}ms`);
    
            // Cleanup: clear interval before re-running or on unmount
            return () => {
                clearInterval(id);
                console.log("Cleared interval");
            };
        });
    
        // Event listener example
        let mouseX: number = $state(0);
        let mouseY: number = $state(0);
    
        $effect(() => {
            function handleMouseMove(e: MouseEvent): void {
                mouseX = e.clientX;
                mouseY = e.clientY;
            }
    
            window.addEventListener("mousemove", handleMouseMove);
    
            return () => {
                window.removeEventListener("mousemove", handleMouseMove);
            };
        });
    </script>
    
    <p>Count: {count}</p>
    <p>Mouse: {mouseX}, {mouseY}</p>
    
    <label>
        Interval (ms):
        <input type="number" bind:value={interval} min="100" step="100" />
    </label>
    


  15. Pre-Effects with $effect.pre

  16. <script lang="ts">
        let messages: string[] = $state(["Hello", "World"]);
        let container: HTMLDivElement;
    
        // Runs BEFORE DOM updates - useful for measuring
        $effect.pre(() => {
            if (container) {
                // Capture scroll position before DOM changes
                const isAtBottom =
                    container.scrollHeight - container.scrollTop === container.clientHeight;
    
                console.log("Before update, at bottom:", isAtBottom);
            }
        });
    
        // Runs AFTER DOM updates
        $effect(() => {
            if (container) {
                // Auto-scroll to bottom after new messages
                container.scrollTop = container.scrollHeight;
            }
        });
    
        function addMessage(): void {
            messages.push(`Message ${messages.length + 1}`);
        }
    </script>
    
    <div bind:this={container} class="chat" style="height: 200px; overflow-y: auto;">
        {#each messages as message}
            <p>{message}</p>
        {/each}
    </div>
    
    <button onclick={addMessage}>Add Message</button>
    


  17. Effect Tracking with $effect.tracking

  18. <script lang="ts">
        let count: number = $state(0);
    
        function logWithContext(message: string): void {
            if ($effect.tracking()) {
                console.log("[Tracked]", message);
            } else {
                console.log("[Untracked]", message);
            }
        }
    
        // Inside $effect - tracked
        $effect(() => {
            logWithContext(`Count is ${count}`);
        });
    
        // Inside $derived - tracked
        let doubled = $derived.by(() => {
            logWithContext("Computing doubled");
            return count * 2;
        });
    
        function handleClick(): void {
            // Event handler - not tracked
            logWithContext("Button clicked");
            count++;
        }
    </script>
    
    <p>Count: {count}, Doubled: {doubled}</p>
    <button onclick={handleClick}>Increment</button>
    


  19. Root Effects with $effect.root

  20. <script lang="ts">
        let count: number = $state(0);
        let cleanupRoot: (() => void) | null = null;
    
        function startRootEffect(): void {
            // Create an effect that persists outside component lifecycle
            cleanupRoot = $effect.root(() => {
                $effect(() => {
                    console.log("[Root Effect] Count:", count);
                });
    
                // Return cleanup for the root
                return () => {
                    console.log("[Root Effect] Cleaned up");
                };
            });
        }
    
        function stopRootEffect(): void {
            if (cleanupRoot) {
                cleanupRoot();
                cleanupRoot = null;
            }
        }
    </script>
    
    <p>Count: {count}</p>
    <button onclick={() => count++}>Increment</button>
    
    <button onclick={startRootEffect}>Start Root Effect</button>
    <button onclick={stopRootEffect}>Stop Root Effect</button>
    


  21. Untracking Dependencies with untrack

  22. <script lang="ts">
        import { untrack } from "svelte";
    
        let count: number = $state(0);
        let name: string = $state("Alice");
        let effectRuns: number = $state(0);
    
        $effect(() => {
            // This creates a dependency on 'count'
            console.log("Count:", count);
    
            // This does NOT create a dependency on 'name'
            const currentName = untrack(() => name);
            console.log("Name (untracked):", currentName);
    
            effectRuns++;
        });
    
        // The effect only re-runs when 'count' changes, not 'name'
    </script>
    
    <p>Count: {count}</p>
    <p>Name: {name}</p>
    <p>Effect runs: {effectRuns}</p>
    
    <button onclick={() => count++}>Increment Count (triggers effect)</button>
    <button onclick={() => name = name + "!"}>Change Name (no effect)</button>
    


  23. Debugging with $inspect

  24. <script lang="ts">
        interface User {
            name: string;
            age: number;
        }
    
        let count: number = $state(0);
        let user: User = $state({ name: "Alice", age: 25 });
    
        // Basic inspection - logs on every change
        $inspect(count);
    
        // Inspect multiple values
        $inspect(count, user);
    
        // Custom logging with .with()
        $inspect(user).with((type, value) => {
            console.log(`[${type}]`, JSON.stringify(value, null, 2));
        });
    
        // Useful for debugging: pause on change
        $inspect(count).with((type, value) => {
            if (value > 5) {
                debugger; // Opens browser debugger
            }
        });
    </script>
    
    <button onclick={() => count++}>Count: {count}</button>
    <button onclick={() => user.age++}>Age: {user.age}</button>
    <input bind:value={user.name} />
    


  25. Fine-Grained Reactivity

  26. <script lang="ts">
        interface Coordinates {
            x: number;
            y: number;
        }
    
        let coords: Coordinates = $state({ x: 0, y: 0 });
    
        // This effect only re-runs when coords.x changes
        $effect(() => {
            console.log("X changed:", coords.x);
        });
    
        // This effect only re-runs when coords.y changes
        $effect(() => {
            console.log("Y changed:", coords.y);
        });
    
        function updateX(): void {
            coords.x++; // Only triggers first effect
        }
    
        function updateY(): void {
            coords.y++; // Only triggers second effect
        }
    </script>
    
    <p>X: {coords.x}, Y: {coords.y}</p>
    <button onclick={updateX}>Update X</button>
    <button onclick={updateY}>Update Y</button>
    


  27. Reactive Classes

  28. <script lang="ts">
        class Counter {
            count: number = $state(0);
    
            increment(): void {
                this.count++;
            }
    
            decrement(): void {
                this.count--;
            }
    
            get doubled(): number {
                return this.count * 2;
            }
        }
    
        class TodoList {
            items: string[] = $state([]);
    
            add(item: string): void {
                this.items.push(item);
            }
    
            remove(index: number): void {
                this.items.splice(index, 1);
            }
    
            get count(): number {
                return this.items.length;
            }
        }
    
        const counter = new Counter();
        const todos = new TodoList();
        let newTodo: string = $state("");
    </script>
    
    <div>
        <h3>Counter</h3>
        <p>Count: {counter.count} (doubled: {counter.doubled})</p>
        <button onclick={() => counter.decrement()}>-</button>
        <button onclick={() => counter.increment()}>+</button>
    </div>
    
    <div>
        <h3>Todos ({todos.count})</h3>
        <input bind:value={newTodo} />
        <button onclick={() => { todos.add(newTodo); newTodo = ""; }}>Add</button>
    
        <ul>
            {#each todos.items as item, i}
                <li>{item} <button onclick={() => todos.remove(i)}>×</button></li>
            {/each}
        </ul>
    </div>
    


  29. Reactive State in Modules

  30. // stores/counter.svelte.ts
    export function createCounter(initial: number = 0) {
        let count = $state(initial);
    
        return {
            get count() {
                return count;
            },
            increment() {
                count++;
            },
            decrement() {
                count--;
            },
            reset() {
                count = initial;
            }
        };
    }
    
    // Singleton instance
    export const counter = createCounter(0);
    
    <!-- Component.svelte -->
    <script lang="ts">
        import { counter } from "./stores/counter.svelte";
    </script>
    
    <p>Count: {counter.count}</p>
    <button onclick={counter.increment}>+</button>
    <button onclick={counter.decrement}>-</button>
    <button onclick={counter.reset}>Reset</button>
    


  31. Store Interoperability

  32. <script lang="ts">
        import { writable, type Writable } from "svelte/store";
        import { fromStore, toStore } from "svelte/store";
    
        // Traditional store
        const countStore: Writable<number> = writable(0);
    
        // Convert store to rune-based state
        const countState = fromStore(countStore);
    
        // Access value (automatically subscribes)
        $effect(() => {
            console.log("Store value:", countState.current);
        });
    
        // Or create a store from rune state
        let runeCount: number = $state(0);
        const runeStore = toStore(() => runeCount, (v) => runeCount = v);
    </script>
    
    <p>Store: {$countStore}</p>
    <p>State: {countState.current}</p>
    
    <button onclick={() => countStore.update(n => n + 1)}>
        Update Store
    </button>
    
    <button onclick={() => runeCount++}>
        Update Rune
    </button>
    


  33. Avoiding Infinite Loops

  34. <script lang="ts">
        import { untrack } from "svelte";
    
        let count: number = $state(0);
        let history: number[] = $state([]);
    
        // BAD: Infinite loop!
        // $effect(() => {
        //     history.push(count); // Reading 'count' AND modifying 'history'
        //     history = history;   // This creates a loop
        // });
    
        // GOOD: Use untrack to prevent loop
        $effect(() => {
            const current = count; // Track count
            untrack(() => {
                history.push(current); // Don't track history modification
            });
        });
    
        // ALTERNATIVE: Use $effect.pre or separate the concerns
        let lastCount: number | null = null;
    
        $effect(() => {
            if (count !== lastCount) {
                lastCount = count;
                // Safe to modify other state here
            }
        });
    </script>
    
    <button onclick={() => count++}>Count: {count}</button>
    <p>History: {history.join(", ")}</p>
    


  35. Conditional Effects

  36. <script lang="ts">
        let enabled: boolean = $state(true);
        let count: number = $state(0);
        let logs: string[] = $state([]);
    
        $effect(() => {
            // 'enabled' is always tracked
            if (enabled) {
                // 'count' is only tracked when this branch runs
                logs.push(`Count is ${count}`);
            }
        });
    
        // Better pattern: guard the entire effect
        $effect(() => {
            if (!enabled) return;
    
            logs.push(`Enabled count: ${count}`);
    
            return () => {
                logs.push("Effect cleaned up");
            };
        });
    </script>
    
    <label>
        <input type="checkbox" bind:checked={enabled} />
        Enabled
    </label>
    
    <button onclick={() => count++}>Count: {count}</button>
    
    <ul>
        {#each logs as log}
            <li>{log}</li>
        {/each}
    </ul>
    


  37. Batching Updates

  38. <script lang="ts">
        import { flushSync } from "svelte";
    
        let a: number = $state(0);
        let b: number = $state(0);
        let effectRuns: number = $state(0);
    
        $effect(() => {
            console.log(`a: ${a}, b: ${b}`);
            effectRuns++;
        });
    
        function batchedUpdate(): void {
            // These are batched - effect runs once
            a++;
            b++;
        }
    
        function forcedUpdate(): void {
            a++;
            // Force synchronous flush
            flushSync();
            // This triggers a separate effect run
            b++;
        }
    </script>
    
    <p>a: {a}, b: {b}</p>
    <p>Effect runs: {effectRuns}</p>
    
    <button onclick={batchedUpdate}>Batched (1 effect run)</button>
    <button onclick={forcedUpdate}>Forced (2 effect runs)</button>
    


  39. Advanced Reactivity Summary

  40. API Purpose Usage
    $state Deep reactive state let x = $state(value)
    $state.raw Shallow reactive state let x = $state.raw(value)
    $state.snapshot Get plain object copy $state.snapshot(state)
    $derived Computed value (expression) let x = $derived(expr)
    $derived.by Computed value (function) let x = $derived.by(() => ...)
    $effect Side effect (after DOM) $effect(() => { ... })
    $effect.pre Side effect (before DOM) $effect.pre(() => { ... })
    $effect.tracking Check if tracking if ($effect.tracking()) ...
    $effect.root Manual effect lifecycle $effect.root(() => ...)
    untrack Read without tracking untrack(() => value)
    $inspect Debug reactive values $inspect(value)
    flushSync Force synchronous update flushSync()



Svelte Reusing Content

  1. Overview



  2. Basic Slots (Svelte 4)

  3. <!-- Card.svelte -->
    <script lang="ts">
        export let title: string;
    </script>
    
    <div class="card">
        <h2>{title}</h2>
        <div class="content">
            <slot />
        </div>
    </div>
    
    <style>
        .card { border: 1px solid #ccc; padding: 16px; border-radius: 8px; }
        .content { margin-top: 12px; }
    </style>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Card from "./Card.svelte";
    </script>
    
    <Card title="Welcome">
        <p>This content goes into the slot.</p>
        <p>Multiple elements are allowed.</p>
    </Card>
    


  4. Default Slot Content (Svelte 4)

  5. <!-- Button.svelte -->
    <script lang="ts">
        export let variant: "primary" | "secondary" = "primary";
    </script>
    
    <button class="btn btn-{variant}">
        <slot>
            <!-- Default content if nothing is passed -->
            Click me
        </slot>
    </button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Button from "./Button.svelte";
    </script>
    
    <Button />                    <!-- Shows "Click me" -->
    <Button>Submit</Button>       <!-- Shows "Submit" -->
    <Button>Cancel</Button>       <!-- Shows "Cancel" -->
    


  6. Named Slots (Svelte 4)

  7. <!-- Modal.svelte -->
    <script lang="ts">
        export let open: boolean = false;
    </script>
    
    {#if open}
        <div class="modal-overlay">
            <div class="modal">
                <header>
                    <slot name="header">
                        <h2>Modal Title</h2>
                    </slot>
                </header>
    
                <main>
                    <slot>
                        <p>Modal content goes here.</p>
                    </slot>
                </main>
    
                <footer>
                    <slot name="footer">
                        <button on:click={() => open = false}>Close</button>
                    </slot>
                </footer>
            </div>
        </div>
    {/if}
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Modal from "./Modal.svelte";
    
        let showModal: boolean = false;
    </script>
    
    <button on:click={() => showModal = true}>Open Modal</button>
    
    <Modal bind:open={showModal}>
        <h2 slot="header">Confirm Action</h2>
    
        <p>Are you sure you want to proceed?</p>
    
        <div slot="footer">
            <button on:click={() => showModal = false}>Cancel</button>
            <button on:click={() => { /* confirm */ showModal = false }}>Confirm</button>
        </div>
    </Modal>
    


  8. Slot Props (Svelte 4)

  9. <!-- List.svelte -->
    <script lang="ts">
        interface Item {
            id: number;
            name: string;
        }
    
        export let items: Item[];
    </script>
    
    <ul>
        {#each items as item, index}
            <li>
                <slot {item} {index} isFirst={index === 0} isLast={index === items.length - 1}>
                    {item.name}
                </slot>
            </li>
        {/each}
    </ul>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import List from "./List.svelte";
    
        const users = [
            { id: 1, name: "Alice" },
            { id: 2, name: "Bob" },
            { id: 3, name: "Charlie" }
        ];
    </script>
    
    <List items={users} let:item let:index let:isFirst let:isLast>
        <span class:highlight={isFirst || isLast}>
            {index + 1}. {item.name}
            {#if isFirst}(First){/if}
            {#if isLast}(Last){/if}
        </span>
    </List>
    


  10. Checking for Slot Content with $$slots (Svelte 4)

  11. <!-- Card.svelte -->
    <script lang="ts">
        export let title: string;
    </script>
    
    <div class="card">
        <h2>{title}</h2>
    
        <slot />
    
        {#if $$slots.footer}
            <hr />
            <footer>
                <slot name="footer" />
            </footer>
        {/if}
    
        {#if $$slots.actions}
            <div class="actions">
                <slot name="actions" />
            </div>
        {/if}
    </div>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Card from "./Card.svelte";
    </script>
    
    <!-- No footer - footer section won't render -->
    <Card title="Simple Card">
        <p>Just some content.</p>
    </Card>
    
    <!-- With footer -->
    <Card title="Card with Footer">
        <p>Content here.</p>
        <span slot="footer">Footer text</span>
    </Card>
    


  12. Introduction to Snippets (Svelte 5)

  13. <script lang="ts">
        let items: string[] = $state(["Apple", "Banana", "Cherry"]);
    </script>
    
    {#snippet listItem(item: string, index: number)}
        <li class="item">
            <span class="index">{index + 1}.</span>
            <span class="name">{item}</span>
        </li>
    {/snippet}
    
    <ul>
        {#each items as item, i}
            {@render listItem(item, i)}
        {/each}
    </ul>
    
    <style>
        .item { display: flex; gap: 8px; }
        .index { color: #666; }
    </style>
    


  14. Snippets with Complex Parameters

  15. <script lang="ts">
        interface User {
            id: number;
            name: string;
            email: string;
            avatar: string;
            isOnline: boolean;
        }
    
        let users: User[] = $state([
            { id: 1, name: "Alice",   email: "alice@example.com",   avatar: "👩", isOnline: true },
            { id: 2, name: "Bob",     email: "bob@example.com",     avatar: "👨", isOnline: false },
            { id: 3, name: "Charlie", email: "charlie@example.com", avatar: "🧑", isOnline: true }
        ]);
    </script>
    
    {#snippet userCard(user: User, showEmail: boolean = false)}
        <div class="user-card">
            <span class="avatar">{user.avatar}</span>
            <div class="info">
                <strong>{user.name}</strong>
                {#if showEmail}
                    <small>{user.email}</small>
                {/if}
            </div>
            <span class="status" class:online={user.isOnline}>
                {user.isOnline ? "●" : "○"}
            </span>
        </div>
    {/snippet}
    
    <h3>Users (compact)</h3>
    {#each users as user}
        {@render userCard(user)}
    {/each}
    
    <h3>Users (with email)</h3>
    {#each users as user}
        {@render userCard(user, true)}
    {/each}
    


  16. Snippets for Conditional Rendering

  17. <script lang="ts">
        type Status = "loading" | "success" | "error";
    
        let status: Status = $state("loading");
        let data: string | null = $state(null);
        let error: string | null = $state(null);
    
        function fetchData(): void {
            status = "loading";
            setTimeout(() => {
                if (Math.random() > 0.3) {
                    data = "Here is your data!";
                    status = "success";
                } else {
                    error = "Failed to fetch data";
                    status = "error";
                }
            }, 1000);
        }
    </script>
    
    {#snippet loadingState()}
        <div class="loading">
            <span class="spinner">⏳</span>
            <p>Loading...</p>
        </div>
    {/snippet}
    
    {#snippet errorState(message: string)}
        <div class="error">
            <span class="icon">❌</span>
            <p>{message}</p>
            <button onclick={fetchData}>Retry</button>
        </div>
    {/snippet}
    
    {#snippet successState(content: string)}
        <div class="success">
            <span class="icon">✅</span>
            <p>{content}</p>
        </div>
    {/snippet}
    
    <button onclick={fetchData}>Fetch Data</button>
    
    {#if status === "loading"}
        {@render loadingState()}
    {:else if status === "error" && error}
        {@render errorState(error)}
    {:else if status === "success" && data}
        {@render successState(data)}
    {/if}
    


  18. Passing Snippets to Components (Svelte 5)

  19. <!-- List.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface Props<T> {
            items: T[];
            renderItem: Snippet<[T, number]>;
            emptyState?: Snippet;
        }
    
        let { items, renderItem, emptyState }: Props<unknown> = $props();
    </script>
    
    {#if items.length === 0}
        {#if emptyState}
            {@render emptyState()}
        {:else}
            <p>No items to display.</p>
        {/if}
    {:else}
        <ul>
            {#each items as item, index}
                <li>
                    {@render renderItem(item, index)}
                </li>
            {/each}
        </ul>
    {/if}
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import List from "./List.svelte";
    
        interface Product {
            id: number;
            name: string;
            price: number;
        }
    
        let products: Product[] = $state([
            { id: 1, name: "Widget", price: 9.99 },
            { id: 2, name: "Gadget", price: 19.99 }
        ]);
    </script>
    
    {#snippet productItem(product: Product, index: number)}
        <div class="product">
            <strong>{product.name}</strong>
            <span>${product.price.toFixed(2)}</span>
        </div>
    {/snippet}
    
    {#snippet emptyProducts()}
        <div class="empty">
            <p>No products available.</p>
            <button>Add Product</button>
        </div>
    {/snippet}
    
    <List items={products} renderItem={productItem} emptyState={emptyProducts} />
    


  20. The children Snippet (Svelte 5)

  21. <!-- Card.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface Props {
            title: string;
            children: Snippet;
            footer?: Snippet;
        }
    
        let { title, children, footer }: Props = $props();
    </script>
    
    <div class="card">
        <h2>{title}</h2>
    
        <div class="content">
            {@render children()}
        </div>
    
        {#if footer}
            <div class="footer">
                {@render footer()}
            </div>
        {/if}
    </div>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Card from "./Card.svelte";
    </script>
    
    {#snippet cardFooter()}
        <button>Learn More</button>
    {/snippet}
    
    <Card title="Welcome" footer={cardFooter}>
        <p>This becomes the children snippet.</p>
        <p>All content here is passed automatically.</p>
    </Card>
    


  22. Optional Children

  23. <!-- Button.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface Props {
            onclick?: () => void;
            children?: Snippet;
        }
    
        let { onclick, children }: Props = $props();
    </script>
    
    <button onclick={onclick}>
        {#if children}
            {@render children()}
        {:else}
            Click me
        {/if}
    </button>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Button from "./Button.svelte";
    </script>
    
    <Button />                     <!-- Shows "Click me" -->
    <Button>Submit</Button>        <!-- Shows "Submit" -->
    <Button>🚀 Launch</Button>     <!-- Shows "🚀 Launch" -->
    


  24. Snippets with Data from Parent (Render Props Pattern)

  25. <!-- DataFetcher.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface FetchState<T> {
            data: T | null;
            loading: boolean;
            error: string | null;
        }
    
        interface Props<T> {
            url: string;
            children: Snippet<[FetchState<T>]>;
        }
    
        let { url, children }: Props<unknown> = $props();
    
        let state: FetchState<unknown> = $state({
            data: null,
            loading: true,
            error: null
        });
    
        $effect(() => {
            state.loading = true;
            state.error = null;
    
            fetch(url)
                .then(res => res.json())
                .then(data => {
                    state.data = data;
                    state.loading = false;
                })
                .catch(err => {
                    state.error = err.message;
                    state.loading = false;
                });
        });
    </script>
    
    {@render children(state)}
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import DataFetcher from "./DataFetcher.svelte";
    
        interface User {
            id: number;
            name: string;
        }
    </script>
    
    <DataFetcher url="/api/users">
        {#snippet children(state: { data: User[] | null; loading: boolean; error: string | null })}
            {#if state.loading}
                <p>Loading...</p>
            {:else if state.error}
                <p class="error">Error: {state.error}</p>
            {:else if state.data}
                <ul>
                    {#each state.data as user}
                        <li>{user.name}</li>
                    {/each}
                </ul>
            {/if}
        {/snippet}
    </DataFetcher>
    


  26. Typing Snippets

  27. import type { Snippet } from "svelte";
    
    // Snippet with no parameters
    type NoParams = Snippet;
    
    // Snippet with one parameter
    type SingleParam = Snippet<[string]>;
    
    // Snippet with multiple parameters
    type MultiParams = Snippet<[string, number, boolean]>;
    
    // Snippet with object parameter
    interface ItemData {
        id: number;
        name: string;
    }
    type ObjectParam = Snippet<[ItemData]>;
    
    // Optional snippet
    interface Props {
        required: Snippet;
        optional?: Snippet;
    }
    
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface TableColumn<T> {
            key: keyof T;
            header: string;
            render?: Snippet<[T[keyof T], T]>;
        }
    
        interface Props<T> {
            data: T[];
            columns: TableColumn<T>[];
        }
    
        let { data, columns }: Props<Record<string, unknown>> = $props();
    </script>
    
    <table>
        <thead>
            <tr>
                {#each columns as column}
                    <th>{column.header}</th>
                {/each}
            </tr>
        </thead>
        <tbody>
            {#each data as row}
                <tr>
                    {#each columns as column}
                        <td>
                            {#if column.render}
                                {@render column.render(row[column.key], row)}
                            {:else}
                                {row[column.key]}
                            {/if}
                        </td>
                    {/each}
                </tr>
            {/each}
        </tbody>
    </table>
    


  28. Recursive Snippets

  29. <script lang="ts">
        interface TreeNode {
            id: number;
            name: string;
            children?: TreeNode[];
        }
    
        let tree: TreeNode = $state({
            id: 1,
            name: "Root",
            children: [
                {
                    id: 2,
                    name: "Folder A",
                    children: [
                        { id: 4, name: "File 1" },
                        { id: 5, name: "File 2" }
                    ]
                },
                {
                    id: 3,
                    name: "Folder B",
                    children: [
                        { id: 6, name: "File 3" }
                    ]
                }
            ]
        });
    </script>
    
    {#snippet treeNode(node: TreeNode, depth: number = 0)}
        <div class="node" style="padding-left: {depth * 20}px">
            <span>{node.children ? "📁" : "📄"} {node.name}</span>
        </div>
    
        {#if node.children}
            {#each node.children as child}
                {@render treeNode(child, depth + 1)}
            {/each}
        {/if}
    {/snippet}
    
    <div class="tree">
        {@render treeNode(tree)}
    </div>
    


  30. Snippets vs Components

  31. Use Snippets When Use Components When
    Template is only used in one component Template is reused across multiple files
    No need for separate state/lifecycle Needs its own state management
    Simple, presentational markup Complex logic or side effects
    Avoiding component overhead Need component features (actions, bindings)
    Render props / slot replacement Standalone, testable unit


  32. Migrating from Slots to Snippets

  33. <!-- Svelte 4: Default slot -->
    <div>
        <slot />
    </div>
    
    <!-- Svelte 5: children snippet -->
    <script lang="ts">
        import type { Snippet } from "svelte";
        let { children }: { children: Snippet } = $props();
    </script>
    <div>
        {@render children()}
    </div>
    
    <!-- Svelte 4: Named slot -->
    <header>
        <slot name="header" />
    </header>
    
    <!-- Svelte 5: Named snippet prop -->
    <script lang="ts">
        import type { Snippet } from "svelte";
        let { header }: { header?: Snippet } = $props();
    </script>
    <header>
        {#if header}
            {@render header()}
        {/if}
    </header>
    
    <!-- Svelte 4: Slot props -->
    <slot item={item} index={i} />
    
    <!-- Parent usage -->
    <Component let:item let:index>
        {item.name} at {index}
    </Component>
    
    <!-- Svelte 5: Snippet with parameters -->
    <script lang="ts">
        import type { Snippet } from "svelte";
        let { renderItem }: { renderItem: Snippet<[Item, number]> } = $props();
    </script>
    {@render renderItem(item, i)}
    
    <!-- Parent usage -->
    {#snippet itemRenderer(item: Item, index: number)}
        {item.name} at {index}
    {/snippet}
    <Component renderItem={itemRenderer} />
    


  34. Advanced: Snippet Composition

  35. <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface Props {
            layout?: "horizontal" | "vertical" | "grid";
            items: unknown[];
            renderItem: Snippet<[unknown, number]>;
            header?: Snippet;
            footer?: Snippet;
            empty?: Snippet;
        }
    
        let {
            layout = "vertical",
            items,
            renderItem,
            header,
            footer,
            empty
        }: Props = $props();
    </script>
    
    {#snippet wrapper(content: Snippet)}
        <div class="container layout-{layout}">
            {#if header}
                <div class="header">
                    {@render header()}
                </div>
            {/if}
    
            <div class="content">
                {@render content()}
            </div>
    
            {#if footer}
                <div class="footer">
                    {@render footer()}
                </div>
            {/if}
        </div>
    {/snippet}
    
    {#snippet itemList()}
        {#if items.length === 0}
            {#if empty}
                {@render empty()}
            {:else}
                <p>No items</p>
            {/if}
        {:else}
            {#each items as item, i}
                <div class="item">
                    {@render renderItem(item, i)}
                </div>
            {/each}
        {/if}
    {/snippet}
    
    {@render wrapper(itemList)}
    
    <style>
        .layout-horizontal .content { display: flex; flex-direction: row; gap: 8px; }
        .layout-vertical .content { display: flex; flex-direction: column; gap: 8px; }
        .layout-grid .content { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
    </style>
    


  36. Reusing Content Summary

  37. Feature Svelte 4 Svelte 5
    Default content <slot /> {@render children()}
    Named content <slot name="x" /> {@render x()} prop
    Pass to named slot="x" x={snippet}
    Data to parent <slot {data} /> + let:data Snippet<[Data]>
    Check existence $$slots.name {#if snippetProp}
    Default fallback Content inside <slot> {:else} block
    Local reuse N/A {#snippet}
    Type safety Limited Full with Snippet<T>



Svelte Advanced Bindings


  1. Bindable Props with $bindable (Svelte 5)

  2. <!-- Counter.svelte -->
    <script lang="ts">
        interface Props {
            count?: number;
        }
    
        let { count = $bindable(0) }: Props = $props();
    
        function increment(): void {
            count++;
        }
    
        function decrement(): void {
            count--;
        }
    </script>
    
    <div class="counter">
        <button onclick={decrement}>-</button>
        <span>{count}</span>
        <button onclick={increment}>+</button>
    </div>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import Counter from "./Counter.svelte";
    
        let value: number = $state(10);
    </script>
    
    <Counter bind:count={value} />
    
    <p>Parent value: {value}</p>
    <button onclick={() => value = 0}>Reset from parent</button>
    


  3. Multiple Bindable Props

  4. <!-- DateTimePicker.svelte -->
    <script lang="ts">
        interface Props {
            date?: string;
            time?: string;
            timezone?: string;
        }
    
        let {
            date = $bindable(""),
            time = $bindable(""),
            timezone = $bindable("UTC")
        }: Props = $props();
    
        const timezones: string[] = ["UTC", "EST", "PST", "CET", "JST"];
    </script>
    
    <div class="datetime-picker">
        <input type="date" bind:value={date} />
        <input type="time" bind:value={time} />
        <select bind:value={timezone}>
            {#each timezones as tz}
                <option value={tz}>{tz}</option>
            {/each}
        </select>
    </div>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import DateTimePicker from "./DateTimePicker.svelte";
    
        let selectedDate: string = $state("2024-01-15");
        let selectedTime: string = $state("14:30");
        let selectedTimezone: string = $state("EST");
    
        let combined = $derived(
            `${selectedDate} ${selectedTime} ${selectedTimezone}`
        );
    </script>
    
    <DateTimePicker
        bind:date={selectedDate}
        bind:time={selectedTime}
        bind:timezone={selectedTimezone}
    />
    
    <p>Selected: {combined}</p>
    


  5. Bindable with Validation

  6. <!-- ValidatedInput.svelte -->
    <script lang="ts">
        interface Props {
            value?: string;
            min?: number;
            max?: number;
            pattern?: RegExp;
        }
    
        let {
            value = $bindable(""),
            min = 0,
            max = Infinity,
            pattern
        }: Props = $props();
    
        let error: string = $state("");
    
        function validate(newValue: string): void {
            error = "";
    
            if (newValue.length < min) {
                error = `Minimum ${min} characters required`;
            } else if (newValue.length > max) {
                error = `Maximum ${max} characters allowed`;
            } else if (pattern && !pattern.test(newValue)) {
                error = "Invalid format";
            }
        }
    
        // Validate on change
        $effect(() => {
            validate(value);
        });
    
        let isValid = $derived(error === "");
    </script>
    
    <div class="validated-input">
        <input
            type="text"
            bind:value
            class:invalid={!isValid}
        />
        {#if error}
            <span class="error">{error}</span>
        {/if}
    </div>
    
    <style>
        .invalid { border-color: red; }
        .error { color: red; font-size: 0.8em; }
    </style>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import ValidatedInput from "./ValidatedInput.svelte";
    
        let email: string = $state("");
        let username: string = $state("");
    </script>
    
    <ValidatedInput
        bind:value={email}
        pattern={/^[^\s@]+@[^\s@]+\.[^\s@]+$/}
    />
    
    <ValidatedInput
        bind:value={username}
        min={3}
        max={20}
    />
    


  7. File Input Binding

  8. <script lang="ts">
        let files: FileList | null = $state(null);
        let previews: string[] = $state([]);
    
        $effect(() => {
            if (!files) {
                previews = [];
                return;
            }
    
            const newPreviews: string[] = [];
    
            for (const file of files) {
                if (file.type.startsWith("image/")) {
                    const url = URL.createObjectURL(file);
                    newPreviews.push(url);
                }
            }
    
            previews = newPreviews;
    
            // Cleanup URLs on change
            return () => {
                previews.forEach(url => URL.revokeObjectURL(url));
            };
        });
    
        function formatSize(bytes: number): string {
            if (bytes < 1024) return bytes + " B";
            if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
            return (bytes / (1024 * 1024)).toFixed(1) + " MB";
        }
    </script>
    
    <input type="file" bind:files multiple accept="image/*" />
    
    {#if files && files.length > 0}
        <h4>Selected Files:</h4>
        <ul>
            {#each Array.from(files) as file, i}
                <li>
                    {file.name} ({formatSize(file.size)})
                </li>
            {/each}
        </ul>
    
        <div class="previews">
            {#each previews as src}
                <img {src} alt="Preview" width="100" />
            {/each}
        </div>
    {/if}
    


  9. File Input Component with Bindable

  10. <!-- FileUploader.svelte -->
    <script lang="ts">
        interface Props {
            files?: FileList | null;
            accept?: string;
            multiple?: boolean;
            maxSize?: number; // bytes
        }
    
        let {
            files = $bindable(null),
            accept = "*/*",
            multiple = false,
            maxSize = 10 * 1024 * 1024 // 10MB default
        }: Props = $props();
    
        let inputElement: HTMLInputElement;
        let dragOver: boolean = $state(false);
        let error: string = $state("");
    
        function validateFiles(fileList: FileList): boolean {
            error = "";
    
            for (const file of fileList) {
                if (file.size > maxSize) {
                    error = `File "${file.name}" exceeds maximum size`;
                    return false;
                }
            }
    
            return true;
        }
    
        function handleDrop(event: DragEvent): void {
            event.preventDefault();
            dragOver = false;
    
            const droppedFiles = event.dataTransfer?.files;
            if (droppedFiles && validateFiles(droppedFiles)) {
                files = droppedFiles;
            }
        }
    
        function handleChange(event: Event): void {
            const target = event.target as HTMLInputElement;
            if (target.files && validateFiles(target.files)) {
                files = target.files;
            }
        }
    </script>
    
    <div
        class="dropzone"
        class:drag-over={dragOver}
        ondragover={(e) => { e.preventDefault(); dragOver = true; }}
        ondragleave={() => dragOver = false}
        ondrop={handleDrop}
        onclick={() => inputElement.click()}
    >
        <input
            bind:this={inputElement}
            type="file"
            {accept}
            {multiple}
            onchange={handleChange}
            hidden
        />
    
        <p>Drop files here or click to browse</p>
    
        {#if error}
            <p class="error">{error}</p>
        {/if}
    </div>
    
    <style>
        .dropzone {
            border: 2px dashed #ccc;
            padding: 40px;
            text-align: center;
            cursor: pointer;
            transition: border-color 0.2s;
        }
        .drag-over { border-color: #007bff; background: #f0f8ff; }
        .error { color: red; }
    </style>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import FileUploader from "./FileUploader.svelte";
    
        let uploadedFiles: FileList | null = $state(null);
    </script>
    
    <FileUploader
        bind:files={uploadedFiles}
        accept="image/*,application/pdf"
        multiple
        maxSize={5 * 1024 * 1024}
    />
    
    {#if uploadedFiles}
        <p>{uploadedFiles.length} file(s) selected</p>
    {/if}
    


  11. Details Element Binding

  12. <script lang="ts">
        let detailsOpen: boolean = $state(false);
    
        interface FAQ {
            question: string;
            answer: string;
            open: boolean;
        }
    
        let faqs: FAQ[] = $state([
            { question: "What is Svelte?", answer: "A compiler-based framework.", open: false },
            { question: "Is it fast?", answer: "Yes, very fast!", open: false },
            { question: "Should I learn it?", answer: "Absolutely!", open: false }
        ]);
    
        function closeAll(): void {
            faqs = faqs.map(faq => ({ ...faq, open: false }));
        }
    
        function openAll(): void {
            faqs = faqs.map(faq => ({ ...faq, open: true }));
        }
    </script>
    
    <h3>Single Details</h3>
    <details bind:open={detailsOpen}>
        <summary>Click to {detailsOpen ? "close" : "open"}</summary>
        <p>Hidden content revealed!</p>
    </details>
    
    <p>Details is {detailsOpen ? "open" : "closed"}</p>
    <button onclick={() => detailsOpen = !detailsOpen}>
        Toggle programmatically
    </button>
    
    <h3>FAQ Accordion</h3>
    <button onclick={openAll}>Open All</button>
    <button onclick={closeAll}>Close All</button>
    
    {#each faqs as faq, i}
        <details bind:open={faq.open}>
            <summary>{faq.question}</summary>
            <p>{faq.answer}</p>
        </details>
    {/each}
    


  13. Dialog Element Binding

  14. <script lang="ts">
        let dialogElement: HTMLDialogElement;
        let isOpen: boolean = $state(false);
        let returnValue: string = $state("");
    
        function openModal(): void {
            dialogElement.showModal();
            isOpen = true;
        }
    
        function closeModal(value: string): void {
            dialogElement.close(value);
            isOpen = false;
            returnValue = value;
        }
    
        function handleClose(): void {
            isOpen = false;
            returnValue = dialogElement.returnValue;
        }
    </script>
    
    <button onclick={openModal}>Open Dialog</button>
    <p>Last result: {returnValue || "None"}</p>
    
    <dialog bind:this={dialogElement} onclose={handleClose}>
        <h2>Confirm Action</h2>
        <p>Are you sure you want to proceed?</p>
    
        <div class="actions">
            <button onclick={() => closeModal("cancel")}>Cancel</button>
            <button onclick={() => closeModal("confirm")}>Confirm</button>
        </div>
    </dialog>
    
    <style>
        dialog::backdrop {
            background: rgba(0, 0, 0, 0.5);
        }
        .actions {
            display: flex;
            gap: 8px;
            justify-content: flex-end;
            margin-top: 16px;
        }
    </style>
    


  15. Binding to Component Instances

  16. <!-- VideoPlayer.svelte -->
    <script lang="ts">
        interface Props {
            src: string;
        }
    
        let { src }: Props = $props();
    
        let videoElement: HTMLVideoElement;
        let currentTime: number = $state(0);
        let duration: number = $state(0);
        let paused: boolean = $state(true);
    
        // Expose methods for parent to call
        export function play(): void {
            videoElement.play();
        }
    
        export function pause(): void {
            videoElement.pause();
        }
    
        export function seek(time: number): void {
            videoElement.currentTime = time;
        }
    
        export function togglePlay(): void {
            if (paused) {
                play();
            } else {
                pause();
            }
        }
    </script>
    
    <div class="video-player">
        <video
            bind:this={videoElement}
            bind:currentTime
            bind:duration
            bind:paused
            {src}
        >
            <track kind="captions" />
        </video>
    
        <div class="controls">
            <button onclick={togglePlay}>
                {paused ? "▶️" : "⏸️"}
            </button>
            <span>{currentTime.toFixed(1)} / {duration.toFixed(1)}</span>
        </div>
    </div>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import VideoPlayer from "./VideoPlayer.svelte";
    
        let player: VideoPlayer;
    </script>
    
    <VideoPlayer bind:this={player} src="video.mp4" />
    
    <div class="external-controls">
        <button onclick={() => player.play()}>Play</button>
        <button onclick={() => player.pause()}>Pause</button>
        <button onclick={() => player.seek(0)}>Restart</button>
        <button onclick={() => player.seek(30)}>Skip to 30s</button>
    </div>
    


  17. Binding to Inline Styles

  18. <script lang="ts">
        let color: string = $state("#007bff");
        let size: number = $state(16);
        let opacity: number = $state(1);
        let rotation: number = $state(0);
    </script>
    
    <div class="controls">
        <label>
            Color: <input type="color" bind:value={color} />
        </label>
        <label>
            Size: <input type="range" bind:value={size} min="8" max="48" />
        </label>
        <label>
            Opacity: <input type="range" bind:value={opacity} min="0" max="1" step="0.1" />
        </label>
        <label>
            Rotation: <input type="range" bind:value={rotation} min="0" max="360" />
        </label>
    </div>
    
    <div
        class="styled-box"
        style:--color={color}
        style:--size="{size}px"
        style:--opacity={opacity}
        style:--rotation="{rotation}deg"
    >
        Styled Box
    </div>
    
    <style>
        .styled-box {
            color: var(--color);
            font-size: var(--size);
            opacity: var(--opacity);
            transform: rotate(var(--rotation));
            padding: 20px;
            border: 2px solid var(--color);
            display: inline-block;
            transition: all 0.2s;
        }
    </style>
    


  19. Indeterminate Checkbox Binding

  20. <script lang="ts">
        interface Task {
            id: number;
            name: string;
            completed: boolean;
        }
    
        let tasks: Task[] = $state([
            { id: 1, name: "Task 1", completed: true },
            { id: 2, name: "Task 2", completed: false },
            { id: 3, name: "Task 3", completed: true }
        ]);
    
        let allChecked = $derived(tasks.every(t => t.completed));
        let noneChecked = $derived(tasks.every(t => !t.completed));
        let indeterminate = $derived(!allChecked && !noneChecked);
    
        function toggleAll(): void {
            const newState = !allChecked;
            tasks = tasks.map(t => ({ ...t, completed: newState }));
        }
    </script>
    
    <label>
        <input
            type="checkbox"
            checked={allChecked}
            {indeterminate}
            onchange={toggleAll}
        />
        Select All
    </label>
    
    <ul>
        {#each tasks as task}
            <li>
                <label>
                    <input type="checkbox" bind:checked={task.completed} />
                    {task.name}
                </label>
            </li>
        {/each}
    </ul>
    
    <p>
        Status:
        {#if allChecked}
            All selected
        {:else if noneChecked}
            None selected
        {:else}
            Partial selection
        {/if}
    </p>
    


  21. Binding to Canvas

  22. <script lang="ts">
        let canvas: HTMLCanvasElement;
        let ctx: CanvasRenderingContext2D | null = null;
        let isDrawing: boolean = $state(false);
        let color: string = $state("#000000");
        let lineWidth: number = $state(2);
    
        $effect(() => {
            if (canvas) {
                ctx = canvas.getContext("2d");
                if (ctx) {
                    ctx.lineCap = "round";
                    ctx.lineJoin = "round";
                }
            }
        });
    
        function startDrawing(e: MouseEvent): void {
            if (!ctx) return;
            isDrawing = true;
            ctx.beginPath();
            ctx.moveTo(e.offsetX, e.offsetY);
        }
    
        function draw(e: MouseEvent): void {
            if (!isDrawing || !ctx) return;
            ctx.strokeStyle = color;
            ctx.lineWidth = lineWidth;
            ctx.lineTo(e.offsetX, e.offsetY);
            ctx.stroke();
        }
    
        function stopDrawing(): void {
            isDrawing = false;
        }
    
        function clearCanvas(): void {
            if (ctx && canvas) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
            }
        }
    </script>
    
    <div class="controls">
        <input type="color" bind:value={color} />
        <input type="range" bind:value={lineWidth} min="1" max="20" />
        <button onclick={clearCanvas}>Clear</button>
    </div>
    
    <canvas
        bind:this={canvas}
        width="400"
        height="300"
        onmousedown={startDrawing}
        onmousemove={draw}
        onmouseup={stopDrawing}
        onmouseleave={stopDrawing}
        style="border: 1px solid #ccc; cursor: crosshair;"
    ></canvas>
    


  23. Binding to Audio Element

  24. <script lang="ts">
        interface Track {
            title: string;
            artist: string;
            src: string;
        }
    
        let tracks: Track[] = [
            { title: "Song 1", artist: "Artist A", src: "song1.mp3" },
            { title: "Song 2", artist: "Artist B", src: "song2.mp3" }
        ];
    
        let currentTrackIndex: number = $state(0);
        let audioElement: HTMLAudioElement;
    
        // Bindable audio properties
        let currentTime: number = $state(0);
        let duration: number = $state(0);
        let paused: boolean = $state(true);
        let volume: number = $state(1);
        let muted: boolean = $state(false);
        let playbackRate: number = $state(1);
        let ended: boolean = $state(false);
        let seeking: boolean = $state(false);
    
        let currentTrack = $derived(tracks[currentTrackIndex]);
        let progress = $derived(duration ? (currentTime / duration) * 100 : 0);
    
        function formatTime(seconds: number): string {
            const mins = Math.floor(seconds / 60);
            const secs = Math.floor(seconds % 60);
            return `${mins}:${secs.toString().padStart(2, "0")}`;
        }
    
        function nextTrack(): void {
            currentTrackIndex = (currentTrackIndex + 1) % tracks.length;
        }
    
        function prevTrack(): void {
            currentTrackIndex = (currentTrackIndex - 1 + tracks.length) % tracks.length;
        }
    
        $effect(() => {
            if (ended) {
                nextTrack();
            }
        });
    </script>
    
    <audio
        bind:this={audioElement}
        bind:currentTime
        bind:duration
        bind:paused
        bind:volume
        bind:muted
        bind:playbackRate
        bind:ended
        bind:seeking
        src={currentTrack.src}
    ></audio>
    
    <div class="audio-player">
        <div class="track-info">
            <strong>{currentTrack.title}</strong>
            <span>{currentTrack.artist}</span>
        </div>
    
        <div class="progress-bar">
            <input
                type="range"
                min="0"
                max={duration || 0}
                bind:value={currentTime}
            />
            <span>{formatTime(currentTime)} / {formatTime(duration)}</span>
        </div>
    
        <div class="controls">
            <button onclick={prevTrack}>⏮️</button>
            <button onclick={() => paused = !paused}>
                {paused ? "▶️" : "⏸️"}
            </button>
            <button onclick={nextTrack}>⏭️</button>
        </div>
    
        <div class="volume">
            <button onclick={() => muted = !muted}>
                {muted ? "🔇" : "🔊"}
            </button>
            <input type="range" min="0" max="1" step="0.1" bind:value={volume} />
        </div>
    
        <div class="speed">
            <label>
                Speed:
                <select bind:value={playbackRate}>
                    <option value={0.5}>0.5x</option>
                    <option value={1}>1x</option>
                    <option value={1.5}>1.5x</option>
                    <option value={2}>2x</option>
                </select>
            </label>
        </div>
    </div>
    


  25. Binding with Getter/Setter Pattern

  26. <script lang="ts">
        // Store value in cents, display in dollars
        let cents: number = $state(1000);
    
        // Create a "virtual" binding with getter/setter
        let dollars = {
            get value(): number {
                return cents / 100;
            },
            set value(v: number) {
                cents = Math.round(v * 100);
            }
        };
    
        // Temperature conversion
        let celsius: number = $state(0);
    
        let fahrenheit = {
            get value(): number {
                return (celsius * 9/5) + 32;
            },
            set value(f: number) {
                celsius = (f - 32) * 5/9;
            }
        };
    </script>
    
    <div>
        <h4>Currency (stored as cents: {cents})</h4>
        <label>
            Dollars: $
            <input
                type="number"
                step="0.01"
                value={dollars.value}
                oninput={(e) => dollars.value = parseFloat(e.currentTarget.value) || 0}
            />
        </label>
    </div>
    
    <div>
        <h4>Temperature</h4>
        <label>
            Celsius:
            <input type="number" bind:value={celsius} />
        </label>
        <label>
            Fahrenheit:
            <input
                type="number"
                value={fahrenheit.value}
                oninput={(e) => fahrenheit.value = parseFloat(e.currentTarget.value) || 0}
            />
        </label>
    </div>
    


  27. Binding to Custom Form Components

  28. <!-- StarRating.svelte -->
    <script lang="ts">
        interface Props {
            rating?: number;
            max?: number;
            readonly?: boolean;
        }
    
        let {
            rating = $bindable(0),
            max = 5,
            readonly = false
        }: Props = $props();
    
        let hoverRating: number | null = $state(null);
    
        function setRating(value: number): void {
            if (!readonly) {
                rating = value;
            }
        }
    
        function handleHover(value: number | null): void {
            if (!readonly) {
                hoverRating = value;
            }
        }
    
        let displayRating = $derived(hoverRating ?? rating);
    </script>
    
    <div class="star-rating" class:readonly>
        {#each Array(max) as _, i}
            {@const value = i + 1}
            <button
                type="button"
                class="star"
                class:filled={value <= displayRating}
                onclick={() => setRating(value)}
                onmouseenter={() => handleHover(value)}
                onmouseleave={() => handleHover(null)}
                disabled={readonly}
            >
                {value <= displayRating ? "★" : "☆"}
            </button>
        {/each}
    </div>
    
    <style>
        .star-rating { display: flex; gap: 4px; }
        .star {
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #ffc107;
        }
        .readonly .star { cursor: default; }
    </style>
    
    <!-- RangeSlider.svelte -->
    <script lang="ts">
        interface Props {
            min?: number;
            max?: number;
            step?: number;
            value?: [number, number];
        }
    
        let {
            min = 0,
            max = 100,
            step = 1,
            value = $bindable([25, 75] as [number, number])
        }: Props = $props();
    
        function handleMinChange(e: Event): void {
            const target = e.target as HTMLInputElement;
            const newMin = parseFloat(target.value);
            if (newMin <= value[1]) {
                value = [newMin, value[1]];
            }
        }
    
        function handleMaxChange(e: Event): void {
            const target = e.target as HTMLInputElement;
            const newMax = parseFloat(target.value);
            if (newMax >= value[0]) {
                value = [value[0], newMax];
            }
        }
    </script>
    
    <div class="range-slider">
        <input
            type="range"
            {min}
            {max}
            {step}
            value={value[0]}
            oninput={handleMinChange}
        />
        <input
            type="range"
            {min}
            {max}
            {step}
            value={value[1]}
            oninput={handleMaxChange}
        />
        <div class="values">
            {value[0]} - {value[1]}
        </div>
    </div>
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import StarRating from "./StarRating.svelte";
        import RangeSlider from "./RangeSlider.svelte";
    
        let movieRating: number = $state(3);
        let priceRange: [number, number] = $state([20, 80]);
    </script>
    
    <h4>Rate this movie:</h4>
    <StarRating bind:rating={movieRating} />
    <p>You rated: {movieRating} stars</p>
    
    <h4>Price range:</h4>
    <RangeSlider bind:value={priceRange} min={0} max={100} />
    <p>Selected: ${priceRange[0]} - ${priceRange[1]}</p>
    


  29. Debounced Binding

  30. <!-- DebouncedInput.svelte -->
    <script lang="ts">
        interface Props {
            value?: string;
            delay?: number;
            placeholder?: string;
        }
    
        let {
            value = $bindable(""),
            delay = 300,
            placeholder = ""
        }: Props = $props();
    
        let internalValue: string = $state(value);
        let timeoutId: ReturnType<typeof setTimeout>;
    
        // Sync internal value when external value changes
        $effect(() => {
            internalValue = value;
        });
    
        function handleInput(e: Event): void {
            const target = e.target as HTMLInputElement;
            internalValue = target.value;
    
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => {
                value = internalValue;
            }, delay);
        }
    
        // Cleanup on unmount
        $effect(() => {
            return () => clearTimeout(timeoutId);
        });
    </script>
    
    <input
        type="text"
        value={internalValue}
        oninput={handleInput}
        {placeholder}
    />
    
    <!-- Parent.svelte -->
    <script lang="ts">
        import DebouncedInput from "./DebouncedInput.svelte";
    
        let searchQuery: string = $state("");
        let searchCount: number = $state(0);
    
        $effect(() => {
            if (searchQuery) {
                searchCount++;
                console.log(`Searching for: ${searchQuery}`);
            }
        });
    </script>
    
    <DebouncedInput
        bind:value={searchQuery}
        delay={500}
        placeholder="Search..."
    />
    
    <p>Query: {searchQuery}</p>
    <p>Search triggered: {searchCount} times</p>
    


  31. Binding to Scroll Position

  32. <script lang="ts">
        let scrollY: number = $state(0);
        let innerHeight: number = $state(0);
        let scrollContainer: HTMLDivElement;
        let containerScrollTop: number = $state(0);
    
        let scrollProgress = $derived(
            scrollY / (document.body.scrollHeight - innerHeight) * 100 || 0
        );
    
        let headerOpacity = $derived(
            Math.max(0, 1 - scrollY / 200)
        );
    
        let showBackToTop = $derived(scrollY > 300);
    
        function scrollToTop(): void {
            window.scrollTo({ top: 0, behavior: "smooth" });
        }
    </script>
    
    <svelte:window bind:scrollY bind:innerHeight />
    
    <!-- Progress bar -->
    <div class="scroll-progress" style="width: {scrollProgress}%"></div>
    
    <!-- Header with fade effect -->
    <header style="opacity: {headerOpacity}">
        <h1>Scroll Demo</h1>
    </header>
    
    <!-- Content -->
    <main>
        <p>Scroll Y: {scrollY.toFixed(0)}px</p>
        <p>Progress: {scrollProgress.toFixed(1)}%</p>
    
        <!-- Lots of content to enable scrolling -->
        {#each Array(50) as _, i}
            <p>Paragraph {i + 1}</p>
        {/each}
    </main>
    
    <!-- Back to top button -->
    {#if showBackToTop}
        <button class="back-to-top" onclick={scrollToTop}>
            ↑ Top
        </button>
    {/if}
    
    <style>
        .scroll-progress {
            position: fixed;
            top: 0;
            left: 0;
            height: 3px;
            background: #007bff;
            z-index: 100;
            transition: width 0.1s;
        }
        header {
            position: fixed;
            top: 3px;
            width: 100%;
            background: white;
            padding: 10px;
            z-index: 99;
        }
        main { margin-top: 80px; }
        .back-to-top {
            position: fixed;
            bottom: 20px;
            right: 20px;
        }
    </style>
    


  33. Advanced Bindings Summary

  34. Binding Type Syntax Use Case
    Bindable prop $bindable(default) Two-way component props
    File input bind:files File uploads
    Details open bind:open Accordions, dropdowns
    Component instance bind:this Call component methods
    CSS custom property style:--prop Dynamic theming
    Indeterminate indeterminate Partial checkbox state
    Canvas reference bind:this Drawing, graphics
    Audio/Video bind:currentTime, etc. Media players
    Window scroll bind:scrollY Scroll effects
    Debounced Custom component Search, performance



Svelte Context API

  1. What Is Context?



  2. Basic Context: setContext and getContext

  3. <!-- Parent.svelte -->
    <script lang="ts">
        import { setContext } from "svelte";
        import Child from "./Child.svelte";
    
        // Set a context value with a key
        setContext("username", "Alice");
        setContext("theme", "dark");
    </script>
    
    <div>
        <h2>Parent Component</h2>
        <Child />
    </div>
    
    <!-- Child.svelte -->
    <script lang="ts">
        import { getContext } from "svelte";
        import GrandChild from "./GrandChild.svelte";
    
        // Get context values
        const username = getContext<string>("username");
        const theme    = getContext<string>("theme");
    </script>
    
    <div>
        <p>Child: User is {username}, theme is {theme}</p>
        <GrandChild />
    </div>
    
    <!-- GrandChild.svelte -->
    <script lang="ts">
        import { getContext } from "svelte";
    
        // Context is available at any depth
        const username = getContext<string>("username");
    </script>
    
    <p>GrandChild: Hello, {username}!</p>
    


  4. Type-Safe Context Keys

  5. // context-keys.ts
    import type { Writable } from "svelte/store";
    
    // Define context keys as symbols for uniqueness
    export const THEME_KEY  = Symbol("theme");
    export const USER_KEY   = Symbol("user");
    export const CONFIG_KEY = Symbol("config");
    
    // Define types for context values
    export interface User {
        id: number;
        name: string;
        email: string;
        role: "admin" | "user" | "guest";
    }
    
    export interface ThemeConfig {
        mode: "light" | "dark";
        primaryColor: string;
        fontSize: number;
    }
    
    export interface AppConfig {
        apiUrl: string;
        features: {
            darkMode: boolean;
            notifications: boolean;
        };
    }
    
    <!-- App.svelte -->
    <script lang="ts">
        import { setContext } from "svelte";
        import { THEME_KEY, USER_KEY, CONFIG_KEY } from "./context-keys";
        import type { User, ThemeConfig, AppConfig } from "./context-keys";
        import Dashboard from "./Dashboard.svelte";
    
        const user: User = {
            id: 1,
            name: "Alice",
            email: "alice@example.com",
            role: "admin"
        };
    
        const theme: ThemeConfig = {
            mode: "dark",
            primaryColor: "#007bff",
            fontSize: 16
        };
    
        const config: AppConfig = {
            apiUrl: "https://api.example.com",
            features: {
                darkMode: true,
                notifications: true
            }
        };
    
        setContext(THEME_KEY, theme);
        setContext(USER_KEY, user);
        setContext(CONFIG_KEY, config);
    </script>
    
    <Dashboard />
    
    <!-- Dashboard.svelte -->
    <script lang="ts">
        import { getContext } from "svelte";
        import { THEME_KEY, USER_KEY } from "./context-keys";
        import type { User, ThemeConfig } from "./context-keys";
    
        // Type-safe context retrieval
        const user = getContext<User>(USER_KEY);
        const theme = getContext<ThemeConfig>(THEME_KEY);
    </script>
    
    <div style="background: {theme.mode === 'dark' ? '#333' : '#fff'}">
        <h1>Welcome, {user.name}!</h1>
        <p>Role: {user.role}</p>
    </div>
    


  6. Checking for Context with hasContext

  7. <script lang="ts">
        import { getContext, hasContext } from "svelte";
    
        interface Theme {
            mode: "light" | "dark";
        }
    
        const THEME_KEY = Symbol("theme");
    
        // Check if context exists
        const hasTheme = hasContext(THEME_KEY);
    
        // Provide fallback if context doesn't exist
        const theme: Theme = hasContext(THEME_KEY)
            ? getContext<Theme>(THEME_KEY)
            : { mode: "light" }; // default fallback
    
        // Or use a helper function
        function getContextWithDefault<T>(key: symbol, defaultValue: T): T {
            return hasContext(key) ? getContext<T>(key) : defaultValue;
        }
    
        const safeTheme = getContextWithDefault<Theme>(THEME_KEY, { mode: "light" });
    </script>
    
    <p>Theme context exists: {hasTheme}</p>
    <p>Current mode: {theme.mode}</p>
    


  8. Getting All Contexts with getAllContexts

  9. <!-- Parent.svelte -->
    <script lang="ts">
        import { setContext } from "svelte";
        import Child from "./Child.svelte";
    
        setContext("theme", "dark");
        setContext("language", "en");
        setContext("userId", 123);
    </script>
    
    <Child />
    
    <!-- Child.svelte -->
    <script lang="ts">
        import { getAllContexts, setContext } from "svelte";
        import GrandChild from "./GrandChild.svelte";
    
        // Add more context at this level
        setContext("role", "admin");
    
        // Get all contexts (including from ancestors)
        const allContexts = getAllContexts();
    
        // Convert to array for display
        const contextEntries = Array.from(allContexts.entries());
    </script>
    
    <div>
        <h3>Available Contexts:</h3>
        <ul>
            {#each contextEntries as [key, value]}
                <li>{String(key)}: {JSON.stringify(value)}</li>
            {/each}
        </ul>
    
        <GrandChild />
    </div>
    


  10. Reactive Context with Svelte 5

  11. <!-- ThemeProvider.svelte -->
    <script lang="ts">
        import { setContext } from "svelte";
        import type { Snippet } from "svelte";
    
        interface Props {
            children: Snippet;
        }
    
        let { children }: Props = $props();
    
        // Create reactive state
        let mode: "light" | "dark" = $state("light");
        let primaryColor: string = $state("#007bff");
    
        // Create a context object with getters and methods
        const themeContext = {
            get mode() { return mode; },
            get primaryColor() { return primaryColor; },
            toggle() {
                mode = mode === "light" ? "dark" : "light";
            },
            setColor(color: string) {
                primaryColor = color;
            }
        };
    
        setContext("theme", themeContext);
    </script>
    
    {@render children()}
    
    <!-- App.svelte -->
    <script lang="ts">
        import ThemeProvider from "./ThemeProvider.svelte";
        import ThemedContent from "./ThemedContent.svelte";
    </script>
    
    <ThemeProvider>
        <ThemedContent />
    </ThemeProvider>
    
    <!-- ThemedContent.svelte -->
    <script lang="ts">
        import { getContext } from "svelte";
    
        interface ThemeContext {
            readonly mode: "light" | "dark";
            readonly primaryColor: string;
            toggle: () => void;
            setColor: (color: string) => void;
        }
    
        const theme = getContext<ThemeContext>("theme");
    </script>
    
    <div
        class="content"
        style="
            background: {theme.mode === 'dark' ? '#1a1a1a' : '#ffffff'};
            color: {theme.mode === 'dark' ? '#ffffff' : '#1a1a1a'};
        "
    >
        <p>Current theme: {theme.mode}</p>
        <p>Primary color: {theme.primaryColor}</p>
    
        <button onclick={theme.toggle}>
            Toggle Theme
        </button>
    
        <input
            type="color"
            value={theme.primaryColor}
            oninput={(e) => theme.setColor(e.currentTarget.value)}
        />
    </div>
    


  12. Context Factory Pattern

  13. // auth-context.svelte.ts
    import { setContext, getContext, hasContext } from "svelte";
    
    const AUTH_KEY = Symbol("auth");
    
    export interface User {
        id: number;
        name: string;
        email: string;
    }
    
    export interface AuthContext {
        readonly user: User | null;
        readonly isAuthenticated: boolean;
        readonly isLoading: boolean;
        login: (email: string, password: string) => Promise<void>;
        logout: () => void;
    }
    
    export function createAuthContext(): AuthContext {
        let user: User | null = $state(null);
        let isLoading: boolean = $state(false);
    
        const context: AuthContext = {
            get user() { return user; },
            get isAuthenticated() { return user !== null; },
            get isLoading() { return isLoading; },
    
            async login(email: string, password: string) {
                isLoading = true;
                try {
                    // Simulate API call
                    await new Promise(r => setTimeout(r, 1000));
                    user = { id: 1, name: "Alice", email };
                } finally {
                    isLoading = false;
                }
            },
    
            logout() {
                user = null;
            }
        };
    
        setContext(AUTH_KEY, context);
        return context;
    }
    
    export function getAuthContext(): AuthContext {
        if (!hasContext(AUTH_KEY)) {
            throw new Error("Auth context not found. Did you forget to wrap with AuthProvider?");
        }
        return getContext<AuthContext>(AUTH_KEY);
    }
    
    <!-- AuthProvider.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
        import { createAuthContext } from "./auth-context.svelte";
    
        interface Props {
            children: Snippet;
        }
    
        let { children }: Props = $props();
    
        // Initialize auth context
        createAuthContext();
    </script>
    
    {@render children()}
    
    <!-- LoginForm.svelte -->
    <script lang="ts">
        import { getAuthContext } from "./auth-context.svelte";
    
        const auth = getAuthContext();
    
        let email: string = $state("");
        let password: string = $state("");
    
        async function handleSubmit(): Promise<void> {
            await auth.login(email, password);
        }
    </script>
    
    {#if auth.isAuthenticated}
        <div>
            <p>Welcome, {auth.user?.name}!</p>
            <button onclick={auth.logout}>Logout</button>
        </div>
    {:else}
        <form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
            <input bind:value={email} placeholder="Email" disabled={auth.isLoading} />
            <input bind:value={password} type="password" placeholder="Password" disabled={auth.isLoading} />
            <button type="submit" disabled={auth.isLoading}>
                {auth.isLoading ? "Loading..." : "Login"}
            </button>
        </form>
    {/if}
    


  14. Internationalization (i18n) Context

  15. // i18n-context.svelte.ts
    import { setContext, getContext } from "svelte";
    
    const I18N_KEY = Symbol("i18n");
    
    type Locale = "en" | "es" | "fr" | "de";
    
    interface Translations {
        [key: string]: string;
    }
    
    const translations: Record<Locale, Translations> = {
        en: {
            greeting: "Hello",
            farewell: "Goodbye",
            welcome: "Welcome, {name}!",
            items: "{count} item(s)"
        },
        es: {
            greeting: "Hola",
            farewell: "Adiós",
            welcome: "¡Bienvenido, {name}!",
            items: "{count} artículo(s)"
        },
        fr: {
            greeting: "Bonjour",
            farewell: "Au revoir",
            welcome: "Bienvenue, {name}!",
            items: "{count} article(s)"
        },
        de: {
            greeting: "Hallo",
            farewell: "Auf Wiedersehen",
            welcome: "Willkommen, {name}!",
            items: "{count} Artikel"
        }
    };
    
    export interface I18nContext {
        readonly locale: Locale;
        readonly availableLocales: Locale[];
        setLocale: (locale: Locale) => void;
        t: (key: string, params?: Record<string, string | number>) => string;
    }
    
    export function createI18nContext(initialLocale: Locale = "en"): I18nContext {
        let locale: Locale = $state(initialLocale);
    
        function t(key: string, params?: Record<string, string | number>): string {
            let text = translations[locale][key] || key;
    
            if (params) {
                Object.entries(params).forEach(([k, v]) => {
                    text = text.replace(`{${k}}`, String(v));
                });
            }
    
            return text;
        }
    
        const context: I18nContext = {
            get locale() { return locale; },
            get availableLocales() { return ["en", "es", "fr", "de"] as Locale[]; },
            setLocale(newLocale: Locale) {
                locale = newLocale;
            },
            t
        };
    
        setContext(I18N_KEY, context);
        return context;
    }
    
    export function getI18n(): I18nContext {
        return getContext<I18nContext>(I18N_KEY);
    }
    
    <!-- I18nProvider.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
        import { createI18nContext, type I18nContext } from "./i18n-context.svelte";
    
        interface Props {
            locale?: "en" | "es" | "fr" | "de";
            children: Snippet;
        }
    
        let { locale = "en", children }: Props = $props();
    
        createI18nContext(locale);
    </script>
    
    {@render children()}
    
    <!-- TranslatedContent.svelte -->
    <script lang="ts">
        import { getI18n } from "./i18n-context.svelte";
    
        const i18n = getI18n();
    
        let itemCount: number = $state(5);
    </script>
    
    <div>
        <select
            value={i18n.locale}
            onchange={(e) => i18n.setLocale(e.currentTarget.value as "en" | "es" | "fr" | "de")}
        >
            {#each i18n.availableLocales as loc}
                <option value={loc}>{loc.toUpperCase()}</option>
            {/each}
        </select>
    
        <h1>{i18n.t("greeting")}</h1>
        <p>{i18n.t("welcome", { name: "Alice" })}</p>
    
        <input type="number" bind:value={itemCount} min="0" />
        <p>{i18n.t("items", { count: itemCount })}</p>
    
        <p>{i18n.t("farewell")}</p>
    </div>
    


  16. Toast/Notification Context

  17. // toast-context.svelte.ts
    import { setContext, getContext } from "svelte";
    
    const TOAST_KEY = Symbol("toast");
    
    export interface Toast {
        id: number;
        message: string;
        type: "info" | "success" | "warning" | "error";
        duration: number;
    }
    
    export interface ToastContext {
        readonly toasts: Toast[];
        show: (message: string, type?: Toast["type"], duration?: number) => void;
        success: (message: string) => void;
        error: (message: string) => void;
        warning: (message: string) => void;
        info: (message: string) => void;
        dismiss: (id: number) => void;
        clear: () => void;
    }
    
    export function createToastContext(): ToastContext {
        let toasts: Toast[] = $state([]);
        let nextId = 0;
    
        function show(message: string, type: Toast["type"] = "info", duration = 3000): void {
            const id = nextId++;
            const toast: Toast = { id, message, type, duration };
    
            toasts = [...toasts, toast];
    
            if (duration > 0) {
                setTimeout(() => dismiss(id), duration);
            }
        }
    
        function dismiss(id: number): void {
            toasts = toasts.filter(t => t.id !== id);
        }
    
        function clear(): void {
            toasts = [];
        }
    
        const context: ToastContext = {
            get toasts() { return toasts; },
            show,
            success: (msg) => show(msg, "success"),
            error: (msg) => show(msg, "error", 5000),
            warning: (msg) => show(msg, "warning"),
            info: (msg) => show(msg, "info"),
            dismiss,
            clear
        };
    
        setContext(TOAST_KEY, context);
        return context;
    }
    
    export function useToast(): ToastContext {
        return getContext<ToastContext>(TOAST_KEY);
    }
    
    <!-- ToastProvider.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
        import { createToastContext, type Toast } from "./toast-context.svelte";
        import { fly, fade } from "svelte/transition";
    
        interface Props {
            children: Snippet;
        }
    
        let { children }: Props = $props();
    
        const toast = createToastContext();
    
        const typeStyles: Record<Toast["type"], string> = {
            info: "background: #3498db; color: white;",
            success: "background: #27ae60; color: white;",
            warning: "background: #f39c12; color: white;",
            error: "background: #e74c3c; color: white;"
        };
    </script>
    
    {@render children()}
    
    <div class="toast-container">
        {#each toast.toasts as t (t.id)}
            <div
                class="toast"
                style={typeStyles[t.type]}
                in:fly={{ y: 50, duration: 200 }}
                out:fade={{ duration: 150 }}
            >
                <span>{t.message}</span>
                <button onclick={() => toast.dismiss(t.id)}>×</button>
            </div>
        {/each}
    </div>
    
    <style>
        .toast-container {
            position: fixed;
            bottom: 20px;
            right: 20px;
            display: flex;
            flex-direction: column;
            gap: 8px;
            z-index: 1000;
        }
        .toast {
            padding: 12px 16px;
            border-radius: 4px;
            display: flex;
            align-items: center;
            gap: 12px;
            min-width: 200px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        }
        .toast button {
            background: none;
            border: none;
            color: inherit;
            font-size: 18px;
            cursor: pointer;
            margin-left: auto;
        }
    </style>
    
    <!-- SomeComponent.svelte -->
    <script lang="ts">
        import { useToast } from "./toast-context.svelte";
    
        const toast = useToast();
    
        function handleSave(): void {
            toast.success("Saved successfully!");
        }
    
        function handleError(): void {
            toast.error("Something went wrong!");
        }
    </script>
    
    <button onclick={handleSave}>Save</button>
    <button onclick={handleError}>Trigger Error</button>
    <button onclick={() => toast.info("Just FYI...")}>Info</button>
    <button onclick={() => toast.warning("Be careful!")}>Warning</button>
    


  18. Modal/Dialog Context

  19. // modal-context.svelte.ts
    import { setContext, getContext } from "svelte";
    import type { Snippet } from "svelte";
    
    const MODAL_KEY = Symbol("modal");
    
    export interface ModalConfig {
        title?: string;
        content: Snippet;
        onClose?: () => void;
        closeOnBackdrop?: boolean;
    }
    
    export interface ModalContext {
        readonly isOpen: boolean;
        readonly config: ModalConfig | null;
        open: (config: ModalConfig) => void;
        close: () => void;
        confirm: (title: string, message: string) => Promise<boolean>;
    }
    
    export function createModalContext(): ModalContext {
        let isOpen: boolean = $state(false);
        let config: ModalConfig | null = $state(null);
        let confirmResolver: ((value: boolean) => void) | null = null;
    
        const context: ModalContext = {
            get isOpen() { return isOpen; },
            get config() { return config; },
    
            open(newConfig: ModalConfig) {
                config = newConfig;
                isOpen = true;
            },
    
            close() {
                config?.onClose?.();
                isOpen = false;
                config = null;
    
                if (confirmResolver) {
                    confirmResolver(false);
                    confirmResolver = null;
                }
            },
    
            confirm(title: string, message: string): Promise<boolean> {
                return new Promise((resolve) => {
                    confirmResolver = resolve;
                    // Note: content would be set by the ModalProvider
                    isOpen = true;
                });
            }
        };
    
        setContext(MODAL_KEY, context);
        return context;
    }
    
    export function useModal(): ModalContext {
        return getContext<ModalContext>(MODAL_KEY);
    }
    


  20. Feature Flags Context

  21. // feature-flags-context.svelte.ts
    import { setContext, getContext } from "svelte";
    
    const FEATURE_FLAGS_KEY = Symbol("featureFlags");
    
    export interface FeatureFlags {
        darkMode: boolean;
        newDashboard: boolean;
        betaFeatures: boolean;
        experimentalApi: boolean;
    }
    
    export interface FeatureFlagsContext {
        readonly flags: FeatureFlags;
        isEnabled: (flag: keyof FeatureFlags) => boolean;
        enable: (flag: keyof FeatureFlags) => void;
        disable: (flag: keyof FeatureFlags) => void;
        toggle: (flag: keyof FeatureFlags) => void;
    }
    
    export function createFeatureFlagsContext(initial: Partial<FeatureFlags> = {}): FeatureFlagsContext {
        let flags: FeatureFlags = $state({
            darkMode: false,
            newDashboard: false,
            betaFeatures: false,
            experimentalApi: false,
            ...initial
        });
    
        const context: FeatureFlagsContext = {
            get flags() { return flags; },
    
            isEnabled(flag: keyof FeatureFlags): boolean {
                return flags[flag];
            },
    
            enable(flag: keyof FeatureFlags): void {
                flags[flag] = true;
            },
    
            disable(flag: keyof FeatureFlags): void {
                flags[flag] = false;
            },
    
            toggle(flag: keyof FeatureFlags): void {
                flags[flag] = !flags[flag];
            }
        };
    
        setContext(FEATURE_FLAGS_KEY, context);
        return context;
    }
    
    export function useFeatureFlags(): FeatureFlagsContext {
        return getContext<FeatureFlagsContext>(FEATURE_FLAGS_KEY);
    }
    
    <!-- FeatureFlaggedComponent.svelte -->
    <script lang="ts">
        import { useFeatureFlags } from "./feature-flags-context.svelte";
    
        const features = useFeatureFlags();
    </script>
    
    {#if features.isEnabled("newDashboard")}
        <div class="new-dashboard">
            <h2>New Dashboard (Beta)</h2>
            <!-- New dashboard content -->
        </div>
    {:else}
        <div class="old-dashboard">
            <h2>Dashboard</h2>
            <!-- Old dashboard content -->
        </div>
    {/if}
    
    {#if features.isEnabled("betaFeatures")}
        <aside class="beta-panel">
            <h3>Beta Features</h3>
            <!-- Beta content -->
        </aside>
    {/if}
    


  22. Nested Context Override

  23. <!-- ThemeProvider.svelte -->
    <script lang="ts">
        import { setContext } from "svelte";
        import type { Snippet } from "svelte";
    
        interface Props {
            theme: "light" | "dark";
            children: Snippet;
        }
    
        let { theme, children }: Props = $props();
    
        // This will override any parent theme context
        setContext("theme", theme);
    </script>
    
    <div class="theme-{theme}">
        {@render children()}
    </div>
    
    <!-- App.svelte -->
    <script lang="ts">
        import ThemeProvider from "./ThemeProvider.svelte";
        import ThemedBox from "./ThemedBox.svelte";
    </script>
    
    <ThemeProvider theme="light">
        <!-- This section uses light theme -->
        <ThemedBox>Light themed content</ThemedBox>
    
        <ThemeProvider theme="dark">
            <!-- This nested section overrides to dark theme -->
            <ThemedBox>Dark themed content (nested override)</ThemedBox>
        </ThemeProvider>
    
        <!-- Back to light theme -->
        <ThemedBox>Light themed again</ThemedBox>
    </ThemeProvider>
    
    <!-- ThemedBox.svelte -->
    <script lang="ts">
        import { getContext } from "svelte";
        import type { Snippet } from "svelte";
    
        interface Props {
            children: Snippet;
        }
    
        let { children }: Props = $props();
    
        const theme = getContext<"light" | "dark">("theme");
    </script>
    
    <div
        class="box"
        style="
            background: {theme === 'dark' ? '#333' : '#fff'};
            color: {theme === 'dark' ? '#fff' : '#333'};
        "
    >
        <p>Theme: {theme}</p>
        {@render children()}
    </div>
    


  24. Context vs Props vs Stores

  25. Feature Props Context Stores / Runes in Modules
    Direction Parent → Child (direct) Parent → Any Descendant Any → Any
    Reactivity Automatic Manual (via runes) Automatic
    Scope Single component Component subtree Global / Module
    Multiple instances Yes Yes (per tree) No (singleton)
    Best for Direct parent-child data Avoiding prop drilling Global app state
    Debugging Easy Moderate Easy


  26. Common Pitfalls

  27. <script lang="ts">
        import { setContext, getContext } from "svelte";
    
        // ✅ CORRECT: During initialization
        setContext("key", "value");
    
        // ❌ WRONG: In event handler
        function handleClick(): void {
            // This will throw an error!
            // setContext("key", "newValue");
        }
    
        // ❌ WRONG: In $effect
        // $effect(() => {
        //     setContext("key", someValue);
        // });
    </script>
    
    <script lang="ts">
        import { setContext } from "svelte";
    
        let count = $state(0);
    
        // ❌ WRONG: Primitive won't be reactive in consumers
        // setContext("count", count);
    
        // ✅ CORRECT: Pass an object with getter
        setContext("counter", {
            get count() { return count; },
            increment() { count++; }
        });
    </script>
    
    // ❌ WRONG: String keys can collide
    setContext("theme", myTheme);
    
    // ✅ CORRECT: Use symbols for uniqueness
    const THEME_KEY = Symbol("theme");
    setContext(THEME_KEY, myTheme);
    


  28. Context API Summary

  29. Function Purpose Usage
    setContext Provide a value setContext(key, value)
    getContext Consume a value getContext<T>(key)
    hasContext Check if exists hasContext(key)
    getAllContexts Get all as Map getAllContexts()
    Symbol keys Prevent collisions Symbol("name")
    Factory pattern Reusable contexts createXContext()
    Reactive context Use getters + runes get value() { return state }



Svelte Special Elements

  1. Overview



  2. <svelte:self> — Recursive Components

  3. <!-- TreeView.svelte -->
    <script lang="ts">
        interface TreeNode {
            name: string;
            children?: TreeNode[];
            expanded?: boolean;
        }
    
        interface Props {
            node: TreeNode;
            depth?: number;
        }
    
        let { node, depth = 0 }: Props = $props();
    
        let expanded: boolean = $state(node.expanded ?? false);
    
        function toggle(): void {
            expanded = !expanded;
        }
    </script>
    
    <div class="tree-node" style="padding-left: {depth * 20}px">
        {#if node.children && node.children.length > 0}
            <button class="toggle" onclick={toggle}>
                {expanded ? "▼" : "▶"}
            </button>
        {:else}
            <span class="leaf">•</span>
        {/if}
    
        <span class="name">{node.name}</span>
    
        {#if expanded && node.children}
            {#each node.children as child}
                <!-- Recursive call to self -->
                <svelte:self node={child} depth={depth + 1} />
            {/each}
        {/if}
    </div>
    
    <style>
        .tree-node { font-family: monospace; }
        .toggle {
            background: none;
            border: none;
            cursor: pointer;
            width: 20px;
        }
        .leaf { width: 20px; display: inline-block; text-align: center; }
    </style>
    
    <!-- App.svelte -->
    <script lang="ts">
        import TreeView from "./TreeView.svelte";
    
        const fileSystem = {
            name: "root",
            expanded: true,
            children: [
                {
                    name: "src",
                    children: [
                        { name: "App.svelte" },
                        { name: "main.ts" },
                        {
                            name: "components",
                            children: [
                                { name: "Header.svelte" },
                                { name: "Footer.svelte" }
                            ]
                        }
                    ]
                },
                {
                    name: "public",
                    children: [
                        { name: "index.html" },
                        { name: "favicon.ico" }
                    ]
                },
                { name: "package.json" }
            ]
        };
    </script>
    
    <TreeView node={fileSystem} />
    


  4. <svelte:self> — Comment Thread Example

  5. <!-- Comment.svelte -->
    <script lang="ts">
        interface CommentData {
            id: number;
            author: string;
            text: string;
            timestamp: string;
            replies?: CommentData[];
        }
    
        interface Props {
            comment: CommentData;
            depth?: number;
        }
    
        let { comment, depth = 0 }: Props = $props();
    
        let showReplies: boolean = $state(true);
        let maxDepth = 5;
    </script>
    
    <div class="comment" style="margin-left: {depth * 24}px">
        <div class="comment-header">
            <strong>{comment.author}</strong>
            <span class="timestamp">{comment.timestamp}</span>
        </div>
    
        <p class="comment-text">{comment.text}</p>
    
        {#if comment.replies && comment.replies.length > 0}
            <button onclick={() => showReplies = !showReplies}>
                {showReplies ? "Hide" : "Show"} {comment.replies.length} replies
            </button>
    
            {#if showReplies && depth < maxDepth}
                {#each comment.replies as reply}
                    <svelte:self comment={reply} depth={depth + 1} />
                {/each}
            {:else if depth >= maxDepth}
                <p class="max-depth">Continue thread →</p>
            {/if}
        {/if}
    </div>
    
    <style>
        .comment {
            border-left: 2px solid #ddd;
            padding: 8px 12px;
            margin: 8px 0;
        }
        .timestamp { color: #666; font-size: 0.8em; margin-left: 8px; }
        .max-depth { color: #007bff; cursor: pointer; }
    </style>
    


  6. <svelte:component> — Dynamic Components

  7. <!-- Alert.svelte -->
    <script lang="ts">
        interface Props {
            message: string;
        }
        let { message }: Props = $props();
    </script>
    
    <div class="alert">⚠️ {message}</div>
    
    <!-- Success.svelte -->
    <script lang="ts">
        interface Props {
            message: string;
        }
        let { message }: Props = $props();
    </script>
    
    <div class="success">✅ {message}</div>
    
    <!-- Error.svelte -->
    <script lang="ts">
        interface Props {
            message: string;
        }
        let { message }: Props = $props();
    </script>
    
    <div class="error">❌ {message}</div>
    
    <!-- App.svelte -->
    <script lang="ts">
        import Alert from "./Alert.svelte";
        import Success from "./Success.svelte";
        import Error from "./Error.svelte";
        import type { Component } from "svelte";
    
        type NotificationType = "alert" | "success" | "error";
    
        const components: Record<NotificationType, Component<{ message: string }>> = {
            alert: Alert,
            success: Success,
            error: Error
        };
    
        let currentType: NotificationType = $state("alert");
        let message: string = $state("This is a notification");
    
        let CurrentComponent = $derived(components[currentType]);
    </script>
    
    <select bind:value={currentType}>
        <option value="alert">Alert</option>
        <option value="success">Success</option>
        <option value="error">Error</option>
    </select>
    
    <input bind:value={message} />
    
    <!-- Dynamic component rendering -->
    <svelte:component this={CurrentComponent} {message} />
    


  8. <svelte:component> — Tab System

  9. <script lang="ts">
        import type { Component } from "svelte";
        import HomeTab from "./tabs/HomeTab.svelte";
        import ProfileTab from "./tabs/ProfileTab.svelte";
        import SettingsTab from "./tabs/SettingsTab.svelte";
    
        interface Tab {
            id: string;
            label: string;
            component: Component;
            icon: string;
        }
    
        const tabs: Tab[] = [
            { id: "home", label: "Home", component: HomeTab, icon: "🏠" },
            { id: "profile", label: "Profile", component: ProfileTab, icon: "👤" },
            { id: "settings", label: "Settings", component: SettingsTab, icon: "⚙️" }
        ];
    
        let activeTabId: string = $state("home");
    
        let activeTab = $derived(tabs.find(t => t.id === activeTabId));
    </script>
    
    <div class="tabs">
        <nav class="tab-list">
            {#each tabs as tab}
                <button
                    class="tab-button"
                    class:active={activeTabId === tab.id}
                    onclick={() => activeTabId = tab.id}
                >
                    {tab.icon} {tab.label}
                </button>
            {/each}
        </nav>
    
        <div class="tab-content">
            {#if activeTab}
                <svelte:component this={activeTab.component} />
            {/if}
        </div>
    </div>
    
    <style>
        .tab-list { display: flex; gap: 4px; border-bottom: 1px solid #ddd; }
        .tab-button {
            padding: 8px 16px;
            border: none;
            background: none;
            cursor: pointer;
        }
        .tab-button.active {
            border-bottom: 2px solid #007bff;
            color: #007bff;
        }
        .tab-content { padding: 16px; }
    </style>
    


  10. <svelte:element> — Dynamic HTML Elements

  11. <script lang="ts">
        interface Props {
            level?: 1 | 2 | 3 | 4 | 5 | 6;
            children: import("svelte").Snippet;
        }
    
        let { level = 1, children }: Props = $props();
    
        // Dynamically determine the heading tag
        let tag = $derived(`h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6");
    </script>
    
    <svelte:element this={tag}>
        {@render children()}
    </svelte:element>
    
    <!-- Usage -->
    <script lang="ts">
        import Heading from "./Heading.svelte";
    </script>
    
    <Heading level={1}>Main Title</Heading>
    <Heading level={2}>Subtitle</Heading>
    <Heading level={3}>Section</Heading>
    


  12. <svelte:element> — Polymorphic Components

  13. <!-- Button.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface Props {
            as?: "button" | "a" | "div";
            href?: string;
            disabled?: boolean;
            variant?: "primary" | "secondary" | "ghost";
            onclick?: () => void;
            children: Snippet;
        }
    
        let {
            as = "button",
            href,
            disabled = false,
            variant = "primary",
            onclick,
            children
        }: Props = $props();
    
        // If href is provided, render as anchor
        let tag = $derived(href ? "a" : as);
    </script>
    
    <svelte:element
        this={tag}
        class="btn btn-{variant}"
        class:disabled
        {href}
        {disabled}
        {onclick}
    >
        {@render children()}
    </svelte:element>
    
    <style>
        .btn {
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            text-decoration: none;
            display: inline-block;
        }
        .btn-primary { background: #007bff; color: white; border: none; }
        .btn-secondary { background: #6c757d; color: white; border: none; }
        .btn-ghost { background: transparent; border: 1px solid #007bff; color: #007bff; }
        .disabled { opacity: 0.5; pointer-events: none; }
    </style>
    
    <!-- Usage -->
    <script lang="ts">
        import Button from "./Button.svelte";
    </script>
    
    <!-- Renders as <button> -->
    <Button onclick={() => console.log("clicked")}>Click Me</Button>
    
    <!-- Renders as <a> -->
    <Button href="/about">Go to About</Button>
    
    <!-- Renders as <div> -->
    <Button as="div" variant="ghost">Div Button</Button>
    


  14. <svelte:window> — Window Events

  15. <script lang="ts">
        let innerWidth: number = $state(0);
        let innerHeight: number = $state(0);
        let scrollY: number = $state(0);
        let online: boolean = $state(true);
    
        function handleKeydown(event: KeyboardEvent): void {
            if (event.key === "Escape") {
                console.log("Escape pressed!");
            }
            if (event.ctrlKey && event.key === "s") {
                event.preventDefault();
                console.log("Save shortcut!");
            }
        }
    
        function handleResize(): void {
            console.log(`Window resized: ${innerWidth}x${innerHeight}`);
        }
    
        function handleOnline(): void {
            online = true;
            console.log("Back online!");
        }
    
        function handleOffline(): void {
            online = false;
            console.log("Gone offline!");
        }
    </script>
    
    <!-- Window event listeners and bindings -->
    <svelte:window
        onkeydown={handleKeydown}
        onresize={handleResize}
        ononline={handleOnline}
        onoffline={handleOffline}
        bind:innerWidth
        bind:innerHeight
        bind:scrollY
    />
    
    <div class="status-bar" class:offline={!online}>
        <p>Window: {innerWidth} × {innerHeight}</p>
        <p>Scroll Y: {scrollY}px</p>
        <p>Status: {online ? "🟢 Online" : "🔴 Offline"}</p>
    </div>
    
    <div style="height: 200vh; padding: 20px;">
        <p>Scroll down to see scrollY update</p>
        <p>Press Escape or Ctrl+S to test keyboard events</p>
    </div>
    
    <style>
        .status-bar {
            position: fixed;
            top: 0;
            right: 0;
            background: white;
            padding: 10px;
            border: 1px solid #ddd;
            font-size: 12px;
        }
        .offline { background: #ffebee; }
    </style>
    


  16. <svelte:window> — Available Bindings

  17. <script lang="ts">
        // Readonly bindings
        let innerWidth: number = $state(0);
        let innerHeight: number = $state(0);
        let outerWidth: number = $state(0);
        let outerHeight: number = $state(0);
        let online: boolean = $state(true);
        let devicePixelRatio: number = $state(1);
    
        // Two-way bindings (can be set)
        let scrollX: number = $state(0);
        let scrollY: number = $state(0);
    
        function scrollToTop(): void {
            scrollY = 0; // This will scroll the window
        }
    
        function scrollToPosition(x: number, y: number): void {
            scrollX = x;
            scrollY = y;
        }
    </script>
    
    <svelte:window
        bind:innerWidth
        bind:innerHeight
        bind:outerWidth
        bind:outerHeight
        bind:scrollX
        bind:scrollY
        bind:online
        bind:devicePixelRatio
    />
    
    <div class="info">
        <h3>Window Properties</h3>
        <table>
            <tr><td>Inner Size</td><td>{innerWidth} × {innerHeight}</td></tr>
            <tr><td>Outer Size</td><td>{outerWidth} × {outerHeight}</td></tr>
            <tr><td>Scroll Position</td><td>{scrollX}, {scrollY}</td></tr>
            <tr><td>Online</td><td>{online}</td></tr>
            <tr><td>Pixel Ratio</td><td>{devicePixelRatio}</td></tr>
        </table>
    
        <button onclick={scrollToTop}>Scroll to Top</button>
    </div>
    
    Binding Type Writable
    innerWidth number No
    innerHeight number No
    outerWidth number No
    outerHeight number No
    scrollX number Yes
    scrollY number Yes
    online boolean No
    devicePixelRatio number No


  18. <svelte:document> — Document Events

  19. <script lang="ts">
        let activeElement: Element | null = $state(null);
        let visibilityState: DocumentVisibilityState = $state("visible");
        let fullscreenElement: Element | null = $state(null);
    
        function handleVisibilityChange(): void {
            visibilityState = document.visibilityState;
            console.log("Visibility:", visibilityState);
        }
    
        function handleSelectionChange(): void {
            const selection = document.getSelection();
            if (selection && selection.toString()) {
                console.log("Selected:", selection.toString());
            }
        }
    
        function handleFullscreenChange(): void {
            fullscreenElement = document.fullscreenElement;
        }
    </script>
    
    <svelte:document
        onvisibilitychange={handleVisibilityChange}
        onselectionchange={handleSelectionChange}
        onfullscreenchange={handleFullscreenChange}
        bind:activeElement
        bind:fullscreenElement
    />
    
    <div>
        <p>Active Element: {activeElement?.tagName ?? "None"}</p>
        <p>Visibility: {visibilityState}</p>
        <p>Fullscreen: {fullscreenElement ? "Yes" : "No"}</p>
    
        <input placeholder="Focus me to see activeElement change" />
        <textarea>Select this text to trigger selectionchange</textarea>
    </div>
    


  20. <svelte:body> — Body Events

  21. <script lang="ts">
        let mouseX: number = $state(0);
        let mouseY: number = $state(0);
        let isDragging: boolean = $state(false);
    
        function handleMouseMove(event: MouseEvent): void {
            mouseX = event.clientX;
            mouseY = event.clientY;
        }
    
        function handleDragStart(): void {
            isDragging = true;
        }
    
        function handleDragEnd(): void {
            isDragging = false;
        }
    
        // Useful for modals - prevent body scroll
        let preventScroll: boolean = $state(false);
    
        $effect(() => {
            if (preventScroll) {
                document.body.style.overflow = "hidden";
            } else {
                document.body.style.overflow = "";
            }
        });
    </script>
    
    <svelte:body
        onmousemove={handleMouseMove}
        ondragstart={handleDragStart}
        ondragend={handleDragEnd}
    />
    
    <div>
        <p>Mouse Position: {mouseX}, {mouseY}</p>
        <p>Dragging: {isDragging}</p>
    
        <label>
            <input type="checkbox" bind:checked={preventScroll} />
            Prevent body scroll (for modals)
        </label>
    </div>
    
    <!-- Custom cursor that follows mouse -->
    <div
        class="cursor-follower"
        style="left: {mouseX}px; top: {mouseY}px"
    ></div>
    
    <style>
        .cursor-follower {
            position: fixed;
            width: 20px;
            height: 20px;
            background: rgba(0, 123, 255, 0.3);
            border-radius: 50%;
            pointer-events: none;
            transform: translate(-50%, -50%);
        }
    </style>
    


  22. <svelte:head> — Document Head

  23. <script lang="ts">
        interface Props {
            title?: string;
            description?: string;
        }
    
        let {
            title = "My App",
            description = "A Svelte application"
        }: Props = $props();
    </script>
    
    <svelte:head>
        <title>{title}</title>
        <meta name="description" content={description} />
    </svelte:head>
    
    <h1>{title}</h1>
    


  24. <svelte:head> — SEO Component

  25. <!-- SEO.svelte -->
    <script lang="ts">
        interface Props {
            title: string;
            description?: string;
            keywords?: string[];
            image?: string;
            url?: string;
            type?: "website" | "article" | "profile";
            author?: string;
            publishedTime?: string;
            noindex?: boolean;
        }
    
        let {
            title,
            description = "",
            keywords = [],
            image = "",
            url = "",
            type = "website",
            author = "",
            publishedTime = "",
            noindex = false
        }: Props = $props();
    
        let fullTitle = $derived(`${title} | My Site`);
    </script>
    
    <svelte:head>
        <!-- Basic Meta -->
        <title>{fullTitle}</title>
        <meta name="description" content={description} />
    
        {#if keywords.length > 0}
            <meta name="keywords" content={keywords.join(", ")} />
        {/if}
    
        {#if noindex}
            <meta name="robots" content="noindex, nofollow" />
        {/if}
    
        <!-- Open Graph -->
        <meta property="og:title" content={title} />
        <meta property="og:description" content={description} />
        <meta property="og:type" content={type} />
    
        {#if url}
            <meta property="og:url" content={url} />
        {/if}
    
        {#if image}
            <meta property="og:image" content={image} />
        {/if}
    
        <!-- Twitter Card -->
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:title" content={title} />
        <meta name="twitter:description" content={description} />
    
        {#if image}
            <meta name="twitter:image" content={image} />
        {/if}
    
        <!-- Article specific -->
        {#if type === "article"}
            {#if author}
                <meta property="article:author" content={author} />
            {/if}
            {#if publishedTime}
                <meta property="article:published_time" content={publishedTime} />
            {/if}
        {/if}
    
        <!-- Canonical URL -->
        {#if url}
            <link rel="canonical" href={url} />
        {/if}
    </svelte:head>
    
    <!-- BlogPost.svelte -->
    <script lang="ts">
        import SEO from "./SEO.svelte";
    
        interface Props {
            post: {
                title: string;
                excerpt: string;
                image: string;
                author: string;
                date: string;
                content: string;
            };
        }
    
        let { post }: Props = $props();
    </script>
    
    <SEO
        title={post.title}
        description={post.excerpt}
        image={post.image}
        type="article"
        author={post.author}
        publishedTime={post.date}
    />
    
    <article>
        <h1>{post.title}</h1>
        <p>{post.content}</p>
    </article>
    


  26. <svelte:head> — Dynamic Styles and Scripts

  27. <script lang="ts">
        let theme: "light" | "dark" = $state("light");
        let loadHighlighting: boolean = $state(false);
    </script>
    
    <svelte:head>
        <!-- Dynamic theme stylesheet -->
        <link
            rel="stylesheet"
            href="/themes/{theme}.css"
        />
    
        <!-- Conditionally load external library -->
        {#if loadHighlighting}
            <link
                rel="stylesheet"
                href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css"
            />
            <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
        {/if}
    
        <!-- Custom fonts -->
        <link
            href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"
            rel="stylesheet"
        />
    </svelte:head>
    
    <select bind:value={theme}>
        <option value="light">Light Theme</option>
        <option value="dark">Dark Theme</option>
    </select>
    
    <label>
        <input type="checkbox" bind:checked={loadHighlighting} />
        Load syntax highlighting
    </label>
    


  28. <svelte:options> — Compiler Options

  29. <!-- Must be at the top of the component -->
    <svelte:options
        immutable={true}
        accessors={true}
        namespace="svg"
        customElement="my-component"
    />
    
    <script lang="ts">
        // Component code...
    </script>
    
    Option Type Description
    immutable boolean Assume data never mutates (optimize comparisons)
    accessors boolean Generate getters/setters for props
    namespace string Namespace for the component (e.g., "svg", "mathml")
    customElement string Compile as custom element with given tag name
    runes boolean Force runes mode (Svelte 5)


  30. <svelte:options> — Custom Elements

  31. <svelte:options customElement="my-counter" />
    
    <script lang="ts">
        interface Props {
            count?: number;
            step?: number;
        }
    
        let { count = $bindable(0), step = 1 }: Props = $props();
    
        function increment(): void {
            count += step;
        }
    
        function decrement(): void {
            count -= step;
        }
    </script>
    
    <div class="counter">
        <button onclick={decrement}>-</button>
        <span>{count}</span>
        <button onclick={increment}>+</button>
    </div>
    
    <style>
        /* Styles are encapsulated in Shadow DOM */
        .counter {
            display: flex;
            gap: 8px;
            align-items: center;
        }
        button {
            width: 32px;
            height: 32px;
        }
    </style>
    
    <!-- Use in any HTML page -->
    <my-counter count="10" step="5"></my-counter>
    


  32. <svelte:fragment> — Slot Grouping (Svelte 4)

  33. <!-- Layout.svelte (Svelte 4) -->
    <script lang="ts">
        export let title: string;
    </script>
    
    <div class="layout">
        <header>
            <slot name="header">
                <h1>{title}</h1>
            </slot>
        </header>
    
        <main>
            <slot />
        </main>
    
        <footer>
            <slot name="footer" />
        </footer>
    </div>
    
    <!-- Parent.svelte (Svelte 4) -->
    <script lang="ts">
        import Layout from "./Layout.svelte";
    </script>
    
    <Layout title="My Page">
        <!-- Use svelte:fragment to pass multiple elements to a named slot -->
        <svelte:fragment slot="header">
            <h1>Custom Header</h1>
            <nav>
                <a href="/">Home</a>
                <a href="/about">About</a>
            </nav>
        </svelte:fragment>
    
        <!-- Default slot content -->
        <p>Main content goes here.</p>
    
        <svelte:fragment slot="footer">
            <p>Copyright 2024</p>
            <p>All rights reserved</p>
        </svelte:fragment>
    </Layout>
    


  34. <svelte:boundary> — Error Boundaries (Svelte 5)

  35. <script lang="ts">
        import BuggyComponent from "./BuggyComponent.svelte";
    
        let errorMessage: string = $state("");
        let hasError: boolean = $state(false);
    
        function handleError(error: Error): void {
            hasError = true;
            errorMessage = error.message;
            console.error("Caught error:", error);
        }
    
        function reset(): void {
            hasError = false;
            errorMessage = "";
        }
    </script>
    
    <svelte:boundary onerror={handleError}>
        {#if hasError}
            <div class="error-fallback">
                <h2>Something went wrong</h2>
                <p>{errorMessage}</p>
                <button onclick={reset}>Try Again</button>
            </div>
        {:else}
            <BuggyComponent />
        {/if}
    </svelte:boundary>
    
    <style>
        .error-fallback {
            padding: 20px;
            background: #ffebee;
            border: 1px solid #f44336;
            border-radius: 4px;
        }
    </style>
    


  36. <svelte:boundary> — With Failed Snippet

  37. <script lang="ts">
        import DataDisplay from "./DataDisplay.svelte";
    </script>
    
    <svelte:boundary>
        <DataDisplay />
    
        {#snippet failed(error, reset)}
            <div class="error-boundary">
                <h3>⚠️ Error Loading Data</h3>
                <p>{error.message}</p>
                <details>
                    <summary>Stack trace</summary>
                    <pre>{error.stack}</pre>
                </details>
                <button onclick={reset}>Retry</button>
            </div>
        {/snippet}
    </svelte:boundary>
    
    <style>
        .error-boundary {
            padding: 16px;
            background: #fff3cd;
            border: 1px solid #ffc107;
            border-radius: 8px;
        }
        details { margin: 10px 0; }
        pre {
            background: #f5f5f5;
            padding: 10px;
            overflow-x: auto;
            font-size: 12px;
        }
    </style>
    


  38. <svelte:boundary> — Nested Boundaries

  39. <script lang="ts">
        import Header from "./Header.svelte";
        import Sidebar from "./Sidebar.svelte";
        import MainContent from "./MainContent.svelte";
    </script>
    
    <div class="app">
        <!-- Header errors won't crash the whole app -->
        <svelte:boundary>
            <Header />
            {#snippet failed(error, reset)}
                <header class="fallback">
                    <span>Header unavailable</span>
                    <button onclick={reset}>↻</button>
                </header>
            {/snippet}
        </svelte:boundary>
    
        <div class="content">
            <!-- Sidebar errors are isolated -->
            <svelte:boundary>
                <Sidebar />
                {#snippet failed()}
                    <aside class="fallback">Menu unavailable</aside>
                {/snippet}
            </svelte:boundary>
    
            <!-- Main content has its own boundary -->
            <svelte:boundary>
                <MainContent />
                {#snippet failed(error, reset)}
                    <main class="fallback">
                        <p>Content failed to load</p>
                        <button onclick={reset}>Reload</button>
                    </main>
                {/snippet}
            </svelte:boundary>
        </div>
    </div>
    
    <style>
        .app { display: flex; flex-direction: column; min-height: 100vh; }
        .content { display: flex; flex: 1; }
        .fallback {
            background: #f5f5f5;
            padding: 10px;
            color: #666;
        }
    </style>
    


  40. Special Elements Summary

  41. Element Purpose Key Usage
    <svelte:self> Recursive rendering Trees, nested comments
    <svelte:component> Dynamic components this={Component}
    <svelte:element> Dynamic HTML elements this={"div"}
    <svelte:window> Window events/bindings bind:scrollY
    <svelte:document> Document events/bindings bind:activeElement
    <svelte:body> Body events onmousemove
    <svelte:head> Document head content <title>, <meta>
    <svelte:options> Compiler options customElement
    <svelte:fragment> Slot grouping (Svelte 4) slot="name"
    <svelte:boundary> Error boundaries (Svelte 5) onerror, failed



SvelteKit Introduction

  1. What Is SvelteKit?



  2. Creating a SvelteKit Project

  3. # Create a new project
    npx sv create my-app
    
    # Navigate to the project
    cd my-app
    
    # Install dependencies
    npm install
    
    # Start the development server
    npm run dev
    


  4. Project Structure

  5. my-app/
    ├── src/
    │   ├── lib/                  # Library code (alias: $lib)
    │   │   ├── components/       # Reusable components
    │   │   ├── server/           # Server-only code ($lib/server)
    │   │   └── utils.ts          # Utility functions
    │   ├── routes/               # File-based routing
    │   │   ├── +page.svelte      # Home page (/)
    │   │   ├── +page.ts          # Page load function
    │   │   ├── +layout.svelte    # Root layout
    │   │   ├── +error.svelte     # Error page
    │   │   ├── about/
    │   │   │   └── +page.svelte  # About page (/about)
    │   │   └── api/
    │   │       └── +server.ts    # API endpoint (/api)
    │   ├── app.html              # HTML template
    │   ├── app.css               # Global styles
    │   └── app.d.ts              # Type declarations
    ├── static/                   # Static assets (favicon, images)
    ├── svelte.config.js          # Svelte/SvelteKit config
    ├── vite.config.ts            # Vite config
    ├── tsconfig.json             # TypeScript config
    └── package.json
    


  6. The src/routes Directory

  7. File Path URL
    src/routes/+page.svelte /
    src/routes/about/+page.svelte /about
    src/routes/blog/+page.svelte /blog
    src/routes/blog/[slug]/+page.svelte /blog/:slug
    src/routes/api/users/+server.ts /api/users


  8. Route Files Overview


  9. File Purpose
    +page.svelte The page component (UI)
    +page.ts Load data for the page (runs on server & client)
    +page.server.ts Load data (server-only), form actions
    +layout.svelte Shared layout wrapper
    +layout.ts Load data for layout
    +layout.server.ts Load data for layout (server-only)
    +error.svelte Error page for this route
    +server.ts API endpoint (GET, POST, etc.)


  10. Basic Page Component

  11. <!-- src/routes/+page.svelte -->
    <script lang="ts">
        let count: number = $state(0);
    </script>
    
    <svelte:head>
        <title>Home | My App</title>
        <meta name="description" content="Welcome to my SvelteKit app" />
    </svelte:head>
    
    <main>
        <h1>Welcome to SvelteKit</h1>
        <p>This is the home page.</p>
    
        <button onclick={() => count++}>
            Clicks: {count}
        </button>
    </main>
    
    <style>
        main {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
    </style>
    


  12. Layouts

  13. <!-- src/routes/+layout.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface Props {
            children: Snippet;
        }
    
        let { children }: Props = $props();
    </script>
    
    <div class="app">
        <header>
            <nav>
                <a href="/">Home</a>
                <a href="/about">About</a>
                <a href="/blog">Blog</a>
                <a href="/contact">Contact</a>
            </nav>
        </header>
    
        <main>
            {@render children()}
        </main>
    
        <footer>
            <p>© 2024 My App</p>
        </footer>
    </div>
    
    <style>
        .app {
            display: flex;
            flex-direction: column;
            min-height: 100vh;
        }
        header {
            background: #333;
            color: white;
            padding: 1rem;
        }
        nav {
            display: flex;
            gap: 1rem;
        }
        nav a {
            color: white;
            text-decoration: none;
        }
        main {
            flex: 1;
            padding: 1rem;
        }
        footer {
            background: #333;
            color: white;
            padding: 1rem;
            text-align: center;
        }
    </style>
    


  14. Nested Layouts

  15. src/routes/
    ├── +layout.svelte          # Root layout (applies to all pages)
    ├── +page.svelte            # Home page
    ├── blog/
    │   ├── +layout.svelte      # Blog layout (nested inside root)
    │   ├── +page.svelte        # Blog index (/blog)
    │   └── [slug]/
    │       └── +page.svelte    # Blog post (/blog/my-post)
    └── dashboard/
        ├── +layout.svelte      # Dashboard layout
        ├── +page.svelte        # Dashboard home (/dashboard)
        └── settings/
            └── +page.svelte    # Settings (/dashboard/settings)
    
    <!-- src/routes/blog/+layout.svelte -->
    <script lang="ts">
        import type { Snippet } from "svelte";
    
        interface Props {
            children: Snippet;
        }
    
        let { children }: Props = $props();
    </script>
    
    <div class="blog-layout">
        <aside>
            <h3>Blog Categories</h3>
            <ul>
                <li><a href="/blog?category=tech">Technology</a></li>
                <li><a href="/blog?category=life">Lifestyle</a></li>
                <li><a href="/blog?category=news">News</a></li>
            </ul>
        </aside>
    
        <article>
            {@render children()}
        </article>
    </div>
    
    <style>
        .blog-layout {
            display: grid;
            grid-template-columns: 200px 1fr;
            gap: 2rem;
        }
        aside {
            background: #f5f5f5;
            padding: 1rem;
        }
    </style>
    


  16. Loading Data with +page.ts

  17. // src/routes/+page.ts
    import type { PageLoad } from "./$types";
    
    export const load: PageLoad = async ({ fetch }) => {
        const response = await fetch("/api/posts");
        const posts = await response.json();
    
        return {
            posts,
            title: "Latest Posts"
        };
    };
    
    <!-- src/routes/+page.svelte -->
    <script lang="ts">
        import type { PageData } from "./$types";
    
        interface Props {
            data: PageData;
        }
    
        let { data }: Props = $props();
    </script>
    
    <h1>{data.title}</h1>
    
    <ul>
        {#each data.posts as post}
            <li>
                <a href="/blog/{post.slug}">{post.title}</a>
            </li>
        {/each}
    </ul>
    


  18. Server-Only Loading with +page.server.ts

  19. // src/routes/dashboard/+page.server.ts
    import type { PageServerLoad } from "./$types";
    import { redirect } from "@sveltejs/kit";
    import { db } from "$lib/server/database";
    
    export const load: PageServerLoad = async ({ locals, cookies }) => {
        // Check authentication
        const sessionId = cookies.get("session");
    
        if (!sessionId) {
            throw redirect(303, "/login");
        }
    
        // Access database (server-only)
        const user = await db.user.findUnique({
            where: { sessionId }
        });
    
        if (!user) {
            throw redirect(303, "/login");
        }
    
        const stats = await db.stats.findMany({
            where: { userId: user.id }
        });
    
        return {
            user: {
                name: user.name,
                email: user.email
            },
            stats
        };
    };
    
    <!-- src/routes/dashboard/+page.svelte -->
    <script lang="ts">
        import type { PageData } from "./$types";
    
        interface Props {
            data: PageData;
        }
    
        let { data }: Props = $props();
    </script>
    
    <h1>Welcome back, {data.user.name}!</h1>
    
    <div class="stats">
        {#each data.stats as stat}
            <div class="stat-card">
                <h3>{stat.label}</h3>
                <p>{stat.value}</p>
            </div>
        {/each}
    </div>
    


  20. Dynamic Routes

  21. src/routes/
    ├── blog/
    │   └── [slug]/              # Dynamic: /blog/my-post
    │       └── +page.svelte
    ├── users/
    │   └── [id]/                # Dynamic: /users/123
    │       ├── +page.svelte
    │       └── +page.ts
    └── products/
        └── [category]/
            └── [id]/            # Multiple params: /products/electronics/456
                └── +page.svelte
    
    // src/routes/blog/[slug]/+page.ts
    import type { PageLoad } from "./$types";
    import { error } from "@sveltejs/kit";
    
    export const load: PageLoad = async ({ params, fetch }) => {
        // params.slug contains the dynamic value
        const response = await fetch(`/api/posts/${params.slug}`);
    
        if (!response.ok) {
            throw error(404, {
                message: "Post not found"
            });
        }
    
        const post = await response.json();
    
        return { post };
    };
    
    <!-- src/routes/blog/[slug]/+page.svelte -->
    <script lang="ts">
        import type { PageData } from "./$types";
    
        interface Props {
            data: PageData;
        }
    
        let { data }: Props = $props();
    </script>
    
    <svelte:head>
        <title>{data.post.title} | Blog</title>
    </svelte:head>
    
    <article>
        <h1>{data.post.title}</h1>
        <time>{data.post.date}</time>
        <div class="content">
            {@html data.post.content}
        </div>
    </article>
    


  22. Rest Parameters

  23. src/routes/
    └── docs/
        └── [...path]/           # Matches /docs/a, /docs/a/b, /docs/a/b/c
            └── +page.svelte
    
    // src/routes/docs/[...path]/+page.ts
    import type { PageLoad } from "./$types";
    
    export const load: PageLoad = async ({ params }) => {
        // params.path = "a/b/c" for /docs/a/b/c
        const pathParts = params.path?.split("/") ?? [];
    
        return {
            path: params.path,
            parts: pathParts,
            depth: pathParts.length
        };
    };
    


  24. Optional Parameters

  25. src/routes/
    └── lang/
        └── [[locale]]/          # Matches /lang and /lang/en
            └── +page.svelte
    
    // src/routes/lang/[[locale]]/+page.ts
    import type { PageLoad } from "./$types";
    
    export const load: PageLoad = async ({ params }) => {
        // params.locale is undefined for /lang
        // params.locale is "en" for /lang/en
        const locale = params.locale ?? "en"; // Default to English
    
        return {
            locale,
            translations: await loadTranslations(locale)
        };
    };
    
    async function loadTranslations(locale: string) {
        // Load translations...
        return {};
    }
    


  26. API Routes with +server.ts

  27. // src/routes/api/posts/+server.ts
    import type { RequestHandler } from "./$types";
    import { json, error } from "@sveltejs/kit";
    
    // GET /api/posts
    export const GET: RequestHandler = async ({ url }) => {
        const limit = Number(url.searchParams.get("limit")) || 10;
        const page = Number(url.searchParams.get("page")) || 1;
    
        // Fetch posts from database
        const posts = await fetchPosts({ limit, page });
    
        return json({
            posts,
            page,
            limit
        });
    };
    
    // POST /api/posts
    export const POST: RequestHandler = async ({ request, cookies }) => {
        // Check authentication
        const session = cookies.get("session");
        if (!session) {
            throw error(401, "Unauthorized");
        }
    
        const body = await request.json();
    
        // Validate
        if (!body.title || !body.content) {
            throw error(400, "Title and content required");
        }
    
        // Create post
        const post = await createPost(body);
    
        return json(post, { status: 201 });
    };
    
    async function fetchPosts(options: { limit: number; page: number }) {
        // Database query...
        return [];
    }
    
    async function createPost(data: { title: string; content: string }) {
        // Database insert...
        return { id: 1, ...data };
    }
    
    // src/routes/api/posts/[id]/+server.ts
    import type { RequestHandler } from "./$types";
    import { json, error } from "@sveltejs/kit";
    
    // GET /api/posts/123
    export const GET: RequestHandler = async ({ params }) => {
        const post = await findPost(params.id);
    
        if (!post) {
            throw error(404, "Post not found");
        }
    
        return json(post);
    };
    
    // PUT /api/posts/123
    export const PUT: RequestHandler = async ({ params, request }) => {
        const body = await request.json();
        const post = await updatePost(params.id, body);
    
        return json(post);
    };
    
    // DELETE /api/posts/123
    export const DELETE: RequestHandler = async ({ params }) => {
        await deletePost(params.id);
    
        return new Response(null, { status: 204 });
    };
    
    async function findPost(id: string) { return null; }
    async function updatePost(id: string, data: unknown) { return {}; }
    async function deletePost(id: string) {}
    


  28. Navigation with Links

  29. <script lang="ts">
        import { page } from "$app/stores";
    
        // Check if link is active
        let isActive = $derived($page.url.pathname === "/about");
    </script>
    
    <nav>
        <!-- Standard links get client-side navigation -->
        <a href="/">Home</a>
        <a href="/about" class:active={isActive}>About</a>
        <a href="/blog">Blog</a>
    
        <!-- External links open normally -->
        <a href="https://svelte.dev" target="_blank" rel="noopener">
            Svelte Docs
        </a>
    
        <!-- Disable client-side navigation -->
        <a href="/api/download" data-sveltekit-reload>
            Download (full page reload)
        </a>
    
        <!-- Preload on hover -->
        <a href="/blog" data-sveltekit-preload-data="hover">
            Blog (preloads on hover)
        </a>
    </nav>
    
    <style>
        .active { font-weight: bold; color: #007bff; }
    </style>
    


  30. Programmatic Navigation

  31. <script lang="ts">
        import { goto, invalidate, invalidateAll } from "$app/navigation";
        import { page } from "$app/stores";
    
        async function navigateToHome(): Promise<void> {
            await goto("/");
        }
    
        async function navigateWithState(): Promise<void> {
            await goto("/dashboard", {
                replaceState: true,  // Don't add to history
                keepFocus: true,     // Keep focus on current element
                noScroll: true       // Don't scroll to top
            });
        }
    
        async function navigateBack(): Promise<void> {
            history.back();
        }
    
        async function refreshData(): Promise<void> {
            // Re-run load functions for current page
            await invalidateAll();
        }
    
        async function refreshSpecificData(): Promise<void> {
            // Re-run load functions that depend on this URL
            await invalidate("/api/posts");
        }
    </script>
    
    <button onclick={navigateToHome}>Go Home</button>
    <button onclick={navigateBack}>Go Back</button>
    <button onclick={refreshData}>Refresh Data</button>
    
    <p>Current path: {$page.url.pathname}</p>
    


  32. The $lib Alias

  33. // src/lib/utils.ts
    export function formatDate(date: Date): string {
        return date.toLocaleDateString("en-US", {
            year: "numeric",
            month: "long",
            day: "numeric"
        });
    }
    
    export function slugify(text: string): string {
        return text
            .toLowerCase()
            .replace(/\s+/g, "-")
            .replace(/[^\w-]+/g, "");
    }
    
    <!-- src/routes/+page.svelte -->
    <script lang="ts">
        // Import from $lib
        import { formatDate, slugify } from "$lib/utils";
        import Button from "$lib/components/Button.svelte";
        import Header from "$lib/components/Header.svelte";
    
        const date = formatDate(new Date());
    </script>
    
    <Header />
    <p>Today: {date}</p>
    <Button>Click me</Button>
    


  34. Server-Only Code with $lib/server

  35. // src/lib/server/database.ts
    import { PrismaClient } from "@prisma/client";
    
    export const db = new PrismaClient();
    
    export async function getUserById(id: string) {
        return db.user.findUnique({ where: { id } });
    }
    
    // src/lib/server/auth.ts
    import { db } from "./database";
    import bcrypt from "bcrypt";
    
    export async function verifyPassword(email: string, password: string) {
        const user = await db.user.findUnique({ where: { email } });
    
        if (!user) return null;
    
        const valid = await bcrypt.compare(password, user.passwordHash);
    
        return valid ? user : null;
    }
    
    // src/routes/login/+page.server.ts
    import { verifyPassword } from "$lib/server/auth"; // ✅ Allowed
    import type { Actions } from "./$types";
    
    export const actions: Actions = {
        default: async ({ request, cookies }) => {
            const data = await request.formData();
            const email = data.get("email") as string;
            const password = data.get("password") as string;
    
            const user = await verifyPassword(email, password);
    
            if (user) {
                cookies.set("session", user.id, { path: "/" });
                return { success: true };
            }
    
            return { success: false, error: "Invalid credentials" };
        }
    };
    
    <!-- src/routes/login/+page.svelte -->
    <script lang="ts">
        // ❌ This would cause an error at build time:
        // import { verifyPassword } from "$lib/server/auth";
    
        // ✅ Use form actions instead
    </script>
    
    <form method="POST">
        <input name="email" type="email" required />
        <input name="password" type="password" required />
        <button type="submit">Login</button>
    </form>
    


  36. Error Handling

  37. <!-- src/routes/+error.svelte -->
    <script lang="ts">
        import { page } from "$app/stores";
    </script>
    
    <svelte:head>
        <title>Error {$page.status}</title>
    </svelte:head>
    
    <div class="error">
        <h1>{$page.status}</h1>
        <p>{$page.error?.message ?? "Something went wrong"}</p>
    
        {#if $page.status === 404}
            <p>The page you're looking for doesn't exist.</p>
            <a href="/">Go home</a>
        {:else}
            <button onclick={() => location.reload()}>
                Try again
            </button>
        {/if}
    </div>
    
    <style>
        .error {
            text-align: center;
            padding: 4rem;
        }
        h1 {
            font-size: 4rem;
            color: #e74c3c;
        }
    </style>
    
    // Throwing errors in load functions
    import type { PageLoad } from "./$types";
    import { error } from "@sveltejs/kit";
    
    export const load: PageLoad = async ({ params }) => {
        const post = await getPost(params.id);
    
        if (!post) {
            throw error(404, {
                message: "Post not found",
                code: "POST_NOT_FOUND"
            });
        }
    
        return { post };
    };
    


  38. Environment Variables

  39. # .env
    PUBLIC_API_URL=https://api.example.com
    DATABASE_URL=postgresql://localhost/mydb
    SECRET_KEY=super-secret-key
    
    // Server-side: Access all env vars
    // src/routes/api/data/+server.ts
    import { DATABASE_URL, SECRET_KEY } from "$env/static/private";
    import { PUBLIC_API_URL } from "$env/static/public";
    
    console.log(DATABASE_URL);      // ✅ Server only
    console.log(SECRET_KEY);        // ✅ Server only
    console.log(PUBLIC_API_URL);    // ✅ Available everywhere
    
    <!-- Client-side: Only PUBLIC_ vars -->
    <!-- src/routes/+page.svelte -->
    <script lang="ts">
        import { PUBLIC_API_URL } from "$env/static/public";
    
        // ❌ These would fail:
        // import { DATABASE_URL } from "$env/static/private";
        // import { SECRET_KEY } from "$env/static/private";
    </script>
    
    <p>API URL: {PUBLIC_API_URL}</p>
    


  40. Page Options

  41. // src/routes/blog/+page.ts
    import type { PageLoad } from "./$types";
    
    // Prerender this page at build time
    export const prerender = true;
    
    // Enable/disable SSR
    export const ssr = true;
    
    // Enable/disable client-side rendering
    export const csr = true;
    
    // Trailing slash behavior: 'never', 'always', 'ignore'
    export const trailingSlash = "never";
    
    export const load: PageLoad = async () => {
        return { /* ... */ };
    };
    
    // src/routes/app/+layout.ts
    
    // Disable SSR for entire app section (SPA mode)
    export const ssr = false;
    
    // All child routes will inherit this setting
    


  42. SvelteKit Stores

  43. <script lang="ts">
        import { page, navigating, updated } from "$app/stores";
    
        // $page - current page info
        // $page.url - current URL
        // $page.params - route parameters
        // $page.data - data from load functions
        // $page.status - HTTP status code
        // $page.error - error object (if any)
    
        // $navigating - navigation state (null when not navigating)
        // $navigating.from - source URL
        // $navigating.to - destination URL
    
        // $updated - true when new version is available
    </script>
    
    <!-- Show loading indicator during navigation -->
    {#if $navigating}
        <div class="loading-bar"></div>
    {/if}
    
    <p>Current path: {$page.url.pathname}</p>
    <p>Search params: {$page.url.searchParams.toString()}</p>
    
    {#if $page.params.slug}
        <p>Slug: {$page.params.slug}</p>
    {/if}
    
    {#if $updated}
        <div class="update-banner">
            <p>New version available!</p>
            <button onclick={() => location.reload()}>
                Refresh
            </button>
        </div>
    {/if}
    


  44. Configuration (svelte.config.js)

  45. // svelte.config.js
    import adapter from "@sveltejs/adapter-auto";
    import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
    
    /** @type {import('@sveltejs/kit').Config} */
    const config = {
        // Preprocessors (TypeScript, SCSS, etc.)
        preprocess: vitePreprocess(),
    
        kit: {
            // Adapter for deployment target
            adapter: adapter(),
    
            // Path aliases
            alias: {
                $components: "src/lib/components",
                $stores: "src/lib/stores",
                $utils: "src/lib/utils"
            },
    
            // Content Security Policy
            csp: {
                mode: "auto",
                directives: {
                    "script-src": ["self"]
                }
            },
    
            // Prerender options
            prerender: {
                entries: ["*"],
                handleMissingId: "warn"
            }
        }
    };
    
    export default config;
    


  46. SvelteKit vs Svelte Summary

  47. Feature Svelte SvelteKit
    Type Component framework Application framework
    Routing Manual / third-party File-based (built-in)
    SSR Not included Built-in
    API Routes Not included Built-in
    Data Loading Manual Load functions
    Build Tool Any (Vite, Rollup) Vite (integrated)
    Deployment Static files Adapters (Node, Vercel, etc.)
    Use Case Widgets, components Full applications