Claude Code for Svelte 5 — Workflow Guide
The Setup
You are building a web application with Svelte 5, which introduces runes — a new reactivity system replacing Svelte 4’s implicit reactivity with explicit $state, $derived, and $effect primitives. Claude Code can generate Svelte components, but it writes Svelte 4 syntax or React patterns that do not work with Svelte 5’s rune-based reactivity.
What Claude Code Gets Wrong By Default
-
Uses Svelte 4
letreactivity. Claude writeslet count = 0expecting automatic reactivity. Svelte 5 requireslet count = $state(0)for reactive declarations — plainletvariables are not reactive anymore. -
Uses
$:reactive statements. Claude writes$: doubled = count * 2for derived values. Svelte 5 replaces$:withlet doubled = $derived(count * 2)— the old syntax is deprecated. -
Uses
onMountlifecycle from Svelte 4. Claude importsonMountfromsvelte. Svelte 5 uses$effect()for lifecycle-like behavior that automatically tracks dependencies and runs when they change. -
Writes React JSX instead of Svelte template syntax. Claude generates
<div className={...}>with JSX. Svelte uses HTML-like templates withclass:active={isActive}directives and{#if}/{#each}blocks.
The CLAUDE.md Configuration
# Svelte 5 Project
## Framework
- UI: Svelte 5 with runes (NOT Svelte 4)
- Meta-framework: SvelteKit 2
- Reactivity: $state, $derived, $effect runes
- Styling: scoped CSS in <style> blocks
## Svelte 5 Rules
- Reactive state: let count = $state(0)
- Derived values: let doubled = $derived(count * 2)
- Side effects: $effect(() => { console.log(count) })
- Props: let { name, age = 25 }: Props = $props()
- Bind: bind:value={inputValue}
- Events: onclick={handler} (lowercase, no on: prefix in Svelte 5)
- Snippets: {#snippet name()} ... {/snippet} replaces slots
- Template: {#if}, {#each}, {#await} blocks
## Conventions
- Components in src/lib/components/ directory
- Pages in src/routes/ (SvelteKit file-based routing)
- Stores: use $state in .svelte.ts files for shared state
- Scoped styles by default in <style> blocks
- Use $bindable() for two-way binding props
- TypeScript: <script lang="ts"> in all components
- Never use $: or onMount — use $derived and $effect
Workflow Example
You want to create a todo list component with Svelte 5 runes. Prompt Claude Code:
“Create a Svelte 5 todo list component with add, toggle, and delete functionality. Use $state for the todo list, $derived for the filtered view and completion count, and $effect to save to localStorage on changes.”
Claude Code should use let todos = $state<Todo[]>([]) for the list, let completed = $derived(todos.filter(t => t.done).length) for the count, $effect(() => { localStorage.setItem('todos', JSON.stringify(todos)) }) for persistence, and Svelte template syntax with {#each} blocks for rendering.
Common Pitfalls
-
Mutating $state arrays with push. Claude uses
todos.push(newTodo)expecting reactivity. In Svelte 5, array mutations on$statearrays work but the variable must be declared with$state. However, reassignment (todos = [...todos, newTodo]) is more explicit and always triggers updates. -
$effect running on server. Claude puts browser APIs in
$effectwithout checking the environment. In SvelteKit,$effectruns on the server during SSR. Checkimport { browser } from '$app/environment'before accessinglocalStorage,window, or DOM APIs. -
Event handler syntax change. Claude writes
on:click={handler}from Svelte 4. Svelte 5 usesonclick={handler}(standard HTML attribute names). Theon:directive syntax is deprecated and will show warnings.