Claude
Skills
Sign in
Back

svelte

Included with Lifetime
$97 forever

Svelte 5 - Reactive UI framework with compiler magic, Runes API, SvelteKit full-stack framework, SSR/SSG, minimal JavaScript

toolchainsveltesvelte5sveltekitrunesreactivityssrssgcompiler

What this skill does


# Svelte 5 - Compiler-First Reactive Framework

## Overview

Svelte is a **compiler-based** reactive UI framework that shifts work from runtime to build time. Unlike React/Vue, Svelte compiles components to highly optimized vanilla JavaScript with minimal overhead. **Svelte 5** introduces Runes API for explicit, fine-grained reactivity.

**Key Features**:
- **Runes API**: $state, $derived, $effect for explicit reactivity
- **Zero runtime overhead**: Compiles to vanilla JS
- **Built-in state management**: No external libraries needed
- **SvelteKit**: Full-stack framework with SSR/SSG/SPA
- **Write less code**: Simple, readable component syntax
- **Exceptional performance**: Small bundles, fast runtime

**Installation**:
```bash
# Create new SvelteKit project
npm create svelte@latest my-app
cd my-app
npm install
npm run dev

# Or Svelte only (no SvelteKit)
npm create vite@latest my-app -- --template svelte-ts
```

## Svelte 5 Runes API (Modern Approach)

### State Management with $state

```svelte
<script lang="ts">
  // Reactive state - automatically tracks changes
  let count = $state(0);
  let user = $state({ name: 'Alice', age: 30 });

  // Arrays and objects are deeply reactive
  let todos = $state<Todo[]>([]);

  function addTodo(text: string) {
    todos.push({ id: Date.now(), text, done: false });
    // No need for todos = [...todos] like React!
  }

  function increment() {
    count++; // Triggers reactivity
  }
</script>

<button onclick={increment}>
  Clicked {count} times
</button>
```

### Computed Values with $derived

```svelte
<script lang="ts">
  let firstName = $state('John');
  let lastName = $state('Doe');

  // Automatically updates when dependencies change
  let fullName = $derived(`${firstName} ${lastName}`);
  let greeting = $derived(`Hello, ${fullName}`);

  // Complex derivations
  let items = $state([1, 2, 3, 4, 5]);
  let total = $derived(items.reduce((sum, n) => sum + n, 0));
  let average = $derived(total / items.length);
</script>

<p>{greeting}</p>
<p>Average: {average.toFixed(2)}</p>
```

### Side Effects with $effect

```svelte
<script lang="ts">
  let count = $state(0);
  let logs = $state<string[]>([]);

  // Runs when dependencies change
  $effect(() => {
    console.log(`Count is now: ${count}`);
    logs.push(`Count changed to ${count}`);

    // Optional cleanup function
    return () => {
      console.log('Cleaning up previous effect');
    };
  });

  // Effect with external subscriptions
  $effect(() => {
    const interval = setInterval(() => {
      count++;
    }, 1000);

    return () => clearInterval(interval);
  });
</script>
```

### Component Props with $props

```svelte
<script lang="ts">
  interface Props {
    title: string;
    count?: number;
    onUpdate?: (value: number) => void;
  }

  // Type-safe props with defaults
  let { title, count = 0, onUpdate } = $props<Props>();

  // Props are read-only, but can derive from them
  let displayTitle = $derived(title.toUpperCase());
</script>

<h1>{displayTitle}</h1>
<p>Count: {count}</p>
<button onclick={() => onUpdate?.(count + 1)}>
  Increment
</button>
```

### Two-Way Binding with $bindable

```svelte
<!-- Child: SearchInput.svelte -->
<script lang="ts">
  interface Props {
    value: string;
    placeholder?: string;
  }

  // Allows parent to bind to this prop
  let { value = $bindable(''), placeholder = 'Search...' } = $props<Props>();
</script>

<input bind:value {placeholder} type="search" />

<!-- Parent.svelte -->
<script lang="ts">
  import SearchInput from './SearchInput.svelte';

  let query = $state('');
  let results = $derived(query ? search(query) : []);
</script>

<SearchInput bind:value={query} />
<p>Found {results.length} results for "{query}"</p>
```

## Component Patterns

### Basic Component Structure

```svelte
<!-- Counter.svelte -->
<script lang="ts">
  let count = $state(0);
  let doubled = $derived(count * 2);

  function increment() {
    count++;
  }

  function decrement() {
    count--;
  }
</script>

<div class="counter">
  <button onclick={decrement}>-</button>
  <span>Count: {count} (Doubled: {doubled})</span>
  <button onclick={increment}>+</button>
</div>

<style>
  .counter {
    display: flex;
    gap: 1rem;
    align-items: center;
  }

  button {
    padding: 0.5rem 1rem;
    font-size: 1.2rem;
  }
</style>
```

### Conditional Rendering

```svelte
<script lang="ts">
  let loggedIn = $state(false);
  let user = $state<User | null>(null);
  let loading = $state(true);
</script>

{#if loading}
  <p>Loading...</p>
{:else if loggedIn && user}
  <p>Welcome, {user.name}!</p>
{:else}
  <p>Please log in</p>
{/if}
```

### Lists and Keyed Each Blocks

```svelte
<script lang="ts">
  interface Todo {
    id: number;
    text: string;
    done: boolean;
  }

  let todos = $state<Todo[]>([
    { id: 1, text: 'Learn Svelte', done: true },
    { id: 2, text: 'Build app', done: false }
  ]);

  function toggle(id: number) {
    const todo = todos.find(t => t.id === id);
    if (todo) todo.done = !todo.done;
  }
</script>

<ul>
  {#each todos as todo (todo.id)}
    <li class:done={todo.done}>
      <input
        type="checkbox"
        checked={todo.done}
        onchange={() => toggle(todo.id)}
      />
      {todo.text}
    </li>
  {/each}
</ul>

<style>
  .done {
    text-decoration: line-through;
    opacity: 0.6;
  }
</style>
```

## SvelteKit Framework

### Project Structure

```
my-app/
├── src/
│   ├── routes/
│   │   ├── +page.svelte          # Home page
│   │   ├── +page.ts              # Universal load
│   │   ├── +page.server.ts       # Server load
│   │   ├── +layout.svelte        # Shared layout
│   │   ├── about/
│   │   │   └── +page.svelte      # /about
│   │   └── blog/
│   │       ├── +page.svelte      # /blog
│   │       └── [slug]/
│   │           └── +page.svelte  # /blog/my-post
│   ├── lib/
│   │   ├── components/
│   │   ├── stores/
│   │   └── utils/
│   └── app.html
├── static/                        # Static assets
└── svelte.config.js
```

### Load Functions (Data Fetching)

```typescript
// src/routes/blog/[slug]/+page.ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ params, fetch }) => {
  const response = await fetch(`/api/posts/${params.slug}`);
  const post = await response.json();

  return {
    post
  };
};
```

```svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data } = $props<{ data: PageData }>();
</script>

<article>
  <h1>{data.post.title}</h1>
  <div>{@html data.post.content}</div>
</article>
```

### Server-Only Load Functions

```typescript
// src/routes/admin/+page.server.ts
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ locals }) => {
  if (!locals.user?.isAdmin) {
    throw redirect(303, '/login');
  }

  const users = await db.users.findMany();

  return {
    users
  };
};
```

### Form Actions

```typescript
// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});

export const actions = {
  default: async ({ request, cookies }) => {
    const formData = await request.formData();
    const data = Object.fromEntries(formData);

    const result = schema.safeParse(data);
    if (!result.success) {
      return fail(400, {
        errors: result.error.flatten().fieldErrors
      });
    }

    const user = await authenticateUser(result.data);
    if (!user) {
      return fail(401, { message: 'Invalid credentials' });
    }

    cookies.set('session', user.sessionToken, { path: '/' });
    throw redirect(303, '/dashboard');
  }
} satisfies Actions;
```

```svelte
<!-- src/routes/login/+page.svelte -->
<script lang="ts">
  import type { ActionData } from './$types';

  let { form } = $props<{ form

Related in toolchain