DEV Community

Olivia Craft
Olivia Craft

Posted on

CLAUDE.md for Svelte: 13 Rules That Stop AI From Writing React-Flavored Svelte

Svelte is different from every other frontend framework — no virtual DOM, compiled output, reactivity that's a language feature rather than a runtime. That difference is exactly what trips up Claude Code: it defaults to React-style thinking, uses patterns that make sense in Vue or Angular but are wrong in Svelte, and ignores the compiler's superpowers entirely.

These 13 rules fix that.


Rule 1: Reactivity — no useState, no ref(), just assignment

Reactive state uses plain variable assignment. No reactive() wrapper,
no useState(), no signal(). Mutate arrays and objects directly —
Svelte's compiler tracks assignments, not mutations.
Correct: items = [...items, newItem]
Incorrect: items.push(newItem) without reassignment
Enter fullscreen mode Exit fullscreen mode

Claude trained on React and Vue will reach for hooks or composables. In Svelte, let count = 0 and count++ in a click handler is the entire reactivity system.


Rule 2: Stores for cross-component state — writable, readable, derived

Cross-component state: Svelte stores (writable, readable, derived from svelte/store).
No external state library unless project explicitly requires it.
Subscribe with $ prefix in components — no manual subscribe/unsubscribe.
Store logic lives in src/lib/stores/ directory.
Enter fullscreen mode Exit fullscreen mode

The $store auto-subscription syntax is a Svelte-specific feature Claude often misses. Without it, you get manual subscriptions with forgotten cleanup.


Rule 3: Lifecycle — onMount for side effects, not top-level awaits

Side effects that need the DOM: onMount(). 
Cleanup: return a function from onMount().
No top-level await in component script unless using SvelteKit load functions.
onDestroy() for explicit cleanup when onMount return isn't enough.
Enter fullscreen mode Exit fullscreen mode

Claude sometimes puts DOM-dependent code at top-level in the script block, which runs during SSR and throws.


Rule 4: Component communication — props down, events up

Parent-to-child: export let propName (with default values where appropriate).
Child-to-parent: createEventDispatcher() and dispatch().
Sibling state: shared writable store.
No prop drilling beyond 2 levels — use store or context API.
Enter fullscreen mode Exit fullscreen mode

In Svelte, export let is how you declare a prop. Claude sometimes writes this correctly, sometimes writes React-style prop destructuring. This locks it in.


Rule 5: SvelteKit routing and load functions

(If using SvelteKit)
Page data: +page.server.ts load function — never fetch in onMount for initial data.
Layout data: +layout.server.ts.
Actions: form actions in +page.server.ts — no API routes for form submissions.
API endpoints: +server.ts only for non-form data needs.
No client-side fetch for data that can be server-loaded.
Enter fullscreen mode Exit fullscreen mode

SvelteKit's file-based routing and load functions are the most misunderstood part of the ecosystem. Without explicit rules, Claude writes everything in onMount.


Rule 6: Transitions and animations — use Svelte's built-ins

Entry/exit animations: svelte/transition (fade, fly, slide, scale, draw, crossfade).
Motion: svelte/motion (tweened, spring).
No external animation libraries for effects achievable with Svelte built-ins.
transition: directive on elements, not CSS class toggling.
Enter fullscreen mode Exit fullscreen mode

Claude defaults to CSS classes and setTimeout for animations. Svelte's transition system is far cleaner and more performant.


Rule 7: Event handling — on:event, not addEventListener

Event handlers: on:event|modifier directive.
Modifiers: preventDefault, stopPropagation, once, passive — use the pipe syntax.
No addEventListener() in component script for DOM events on component elements.
Component events: on:custom-event directive at usage site.
Enter fullscreen mode Exit fullscreen mode

Manual addEventListener in Svelte components creates cleanup bugs. The directive syntax handles cleanup automatically.


Rule 8: Slots and snippets (Svelte 5)

(If Svelte 5+)
Content projection: snippets ({#snippet name()}{/snippet}) and {@render name()}.
No slots syntax in new components — snippets replace them.
(If Svelte 4)
Content projection: <slot> with named slots for multiple regions.
Enter fullscreen mode Exit fullscreen mode

Svelte 5 replaced slots with snippets. Claude's training data mixes both generations. Specify which you're using.


Rule 9: Reactive declarations — $: for derived values

Derived values from reactive state: $: derivedValue = expression.
$: statements run when their dependencies change — order matters.
No manual watchers or watch() functions.
Complex derived logic: derived() store, not $: with side effects.
Enter fullscreen mode Exit fullscreen mode

$: is Svelte's reactive declaration syntax. Claude often writes imperative update logic instead of reactive declarations.


Rule 10: TypeScript — typed props and events

TypeScript: strict mode. All export let props typed explicitly.
Event dispatcher typed: createEventDispatcher<{eventName: PayloadType}>().
No implicit any on props.
Component generics (Svelte 5): use generics syntax where appropriate.
Enter fullscreen mode Exit fullscreen mode

Without this, Claude generates untyped props that defeat TypeScript's benefits in Svelte.


Rule 11: SSR safety — no window/document at module level

SSR-safe code only at module level.
window, document, localStorage: only inside onMount() or browser checks.
SvelteKit: use $app/environment browser flag for conditional client code.
No direct DOM manipulation outside lifecycle functions.
Enter fullscreen mode Exit fullscreen mode

The most common Svelte SSR bug: accessing window at component initialization. This rule prevents it.


Rule 12: Styling — scoped by default, global with :global()

Component styles: scoped (default). No !important for component-scoped styles.
Global styles: :global() selector or app.css only — never in component <style> without :global().
CSS custom properties for theming — no inline style objects.
Class toggling: class:name={condition} directive, not string interpolation.
Enter fullscreen mode Exit fullscreen mode

The class:name directive is more readable and performant than template string class manipulation.


Rule 13: Testing — Vitest + Testing Library

Unit tests: Vitest.
Component tests: @testing-library/svelte.
No Svelte-specific test utilities that wrap the component lifecycle unnecessarily.
Test files: component.test.ts colocated with component.
Stores: test logic by importing and setting store values directly.
Enter fullscreen mode Exit fullscreen mode

Without this, Claude reaches for Jest with Svelte-specific transforms that require more configuration.


The CLAUDE.md for Svelte (copy this)

# CLAUDE.md

## Stack
- Framework: Svelte 5 (or Svelte 4 — specify)
- Meta-framework: SvelteKit
- State: Svelte stores (writable/readable/derived)
- Styling: scoped CSS + CSS custom properties
- Testing: Vitest + @testing-library/svelte

## Reactivity rules
- Plain assignment for reactive state — no hooks, no refs
- $: for derived values — no manual watchers
- Mutate arrays/objects via reassignment, not mutation
- Cross-component state: writable stores with $ auto-subscription

## Component rules
- Props: export let propName — typed in TypeScript
- Events: createEventDispatcher, typed generics
- Lifecycle: onMount for DOM side effects, return cleanup function
- Transitions: svelte/transition built-ins — no external animation libs
- Events: on:event|modifier directive — no addEventListener in component script

## SvelteKit rules
- Initial data: server load functions, not onMount fetch
- Forms: form actions in +page.server.ts
- API: +server.ts for non-form endpoints only

## SSR safety
- window/document/localStorage: only inside onMount or browser checks
- No DOM access at module level

## Banned patterns
- useState(), useEffect(), ref() — wrong framework
- External state libraries unless explicitly required
- addEventListener() for component DOM events
- !important in component styles
- CSS class toggling via string interpolation (use class: directive)
Enter fullscreen mode Exit fullscreen mode

Why Svelte needs stricter rules than React

React's patterns are so dominant that AI models apply them everywhere, even in frameworks where they're wrong. Svelte's reactivity system is fundamentally different — the compiler, not the runtime, handles reactivity. Without a CLAUDE.md that makes this explicit, you get React-flavored Svelte: functional, but missing the entire point of why you chose Svelte.


Part of a series: CLAUDE.md files for Go, Rust, TypeScript/Node.js, Python, Java, C#/.NET, PHP, Ruby, Elixir, Scala, Haskell, C++, Vue.js/Nuxt, React/Next.js, Flutter/Dart, Swift/iOS, Spring Boot, Django/FastAPI, Android/Jetpack Compose, NestJS, Angular, and now Svelte.

The full rules pack is at oliviacraft.lat

Top comments (0)