Svelte 5 introduces runes, a set of advanced primitives for controlling reactivity. The runes replace certain non-runes features and provide more explicit control over state and effects.
# Svelte 5 Cursor Rules
## Overview
Svelte 5 introduces runes, a set of advanced primitives for controlling reactivity. The runes replace certain non-runes features and provide more explicit control over state and effects.
### $state
- **Purpose:** Declare reactive state.
- **Usage:**
```javascript
<script>let count = $state(0);</script>
```
- **Replaces:** Top-level `let` declarations in non-runes mode.
- **Class Fields:**
```javascript
class Todo {
done = $state(false);
text = $state();
constructor(text) {
this.text = text;
}
}
```
- **Deep Reactivity:** Only plain objects and arrays become deeply reactive.
### $state.raw
- **Purpose:** Declare state that cannot be mutated, only reassigned.
- **Usage:**
```javascript
<script>let numbers = $state.raw([1, 2, 3]);</script>
```
- **Performance:** Improves with large arrays and objects.
### $state.snapshot
- **Purpose:** Take a static snapshot of $state.
- **Usage:**
```javascript
<script>
let counter = $state({ count: 0 });
function onClick() {
console.log($state.snapshot(counter));
}
</script>
```
### $derived
- **Purpose:** Declare derived state.
- **Usage:**
```javascript
<script>let count = $state(0); let doubled = $derived(count * 2);</script>
```
- **Replaces:** Reactive variables computed using `$:` in non-runes mode.
### $derived.by
- **Purpose:** Create complex derivations with a function.
- **Usage:**
```javascript
<script>
let numbers = $state([1, 2, 3]); let total = $derived.by(() => numbers.reduce((a, b) => a + b,
0));
</script>
```
### $effect
- **Purpose:** Run side-effects when values change.
- **Usage:**
```javascript
<script>
let size = $state(50);
let color = $state('#ff3e00');
$effect(() => {
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = color;
context.fillRect(0, 0, size, size);
});
</script>
```
- **Replacements:** $effect replaces a substantial part of `$: {}` blocks triggering side-effects.
### $effect.pre
- **Purpose:** Run code before the DOM updates.
- **Usage:**
```javascript
<script>
$effect.pre(() =>{' '}
{
// logic here
}
);
</script>
```
- **Replaces:** beforeUpdate.
### $effect.tracking
- **Purpose:** Check if code is running inside a tracking context.
- **Usage:**
```javascript
<script>console.log('tracking:', $effect.tracking());</script>
```
### $props
- **Purpose:** Declare component props.
- **Usage:**
```javascript
<script>let {(prop1, prop2)} = $props();</script>
```
- **Replaces:** export let syntax for declaring props.
### $bindable
- **Purpose:** Declare bindable props.
- **Usage:**
```javascript
<script>let {(bindableProp = $bindable('fallback'))} = $props();</script>
```
### $inspect
- **Purpose:** Equivalent to `console.log` but re-runs when its argument changes.
- **Usage:**
```javascript
<script>let count = $state(0); $inspect(count);</script>
```
### $host
- **Purpose:** Retrieve the this reference of the custom element.
- **Usage:**
```javascript
<script>
function greet(greeting) {
$host().dispatchEvent(new CustomEvent('greeting', { detail: greeting }));
}
</script>
```
- **Note:** Only available inside custom element components on the client-side.
### Shareable Runes
Using classes with Runes can actually be faster, because they don't have to compile $state variables with get and set or with value.
```javascript
import { getContext, hasContext, setContext } from "svelte";
type RCurrent<TValue> = { current: TValue };
export class Rune<TRune> {
readonly #key: symbol;
constructor(name: string) {
this.#key = Symbol(name);
}
exists(): boolean {
return hasContext(this.#key);
}
get(): RCurrent<TRune> {
return getContext(this.#key);
}
init(value: TRune): RCurrent<TRune> {
const _value = $state({ current: value });
return setContext(this.#key, _value);
}
// NOT NEEDED!
update(getter: () => TRune): void {
const context = this.get();
$effect(() => {
context.current = getter();
});
}
}
```
#### Initialize Shareable Rune
We must initialize our $state just like anywhere else, only once, in the parent component.
Example below:
```javascript
<script lang="ts">
import {counter} from '$lib/counter.svelte'; const count = counter.init(0); // `count.current`
is available here
</script>
```
#### Read Shareable Rune Anywhere
We can use it safely in a child component and read the current method.
Example below:
```javascript
<script lang="ts">
import { counter } from '$lib/counter.svelte';
const count = counter.get();
</script>
<h1>Hello from Child: {count.current}</h1>
<button type="button" onclick={() => count.current++}>
Increment From Child
</button>
```
#### Update Shareable Rune Anywhere
You can update the rune using the current method as normal.
Example below:
```javascript
<script lang="ts">
import {counter} from '$lib/counter.svelte'; const count = counter.get(); count.current = 9;
</script>
```
#### Derived Update - Shareable Rune
Sometimes, you may need to update a value based on another reactive value. 99% of use cases, you should just use the .current value, but here is an example just in case this applies to you.
Example below:
```javascript
<script lang="ts">
import { counter } from '$lib/counter.svelte';
let value = $state(8);
counter.update(() => value);
</script>
<h1>Hello from Child2: {value}</h1>
<button type="button" onclick={() => value++}>
Update From Another State
</button>
```
### Shareable State with Runes and a custom Class
[Shareable State with Runes and a custom Class](https://medium.com/@chose/week-7-how-to-manage-shared-state-in-svelte-5-with-runes-77a4ad305b8a)
### Overview of snippets in svelte 5
Snippets, along with render tags, help create reusable chunks of markup inside your components, reducing duplication and enhancing maintainability.
### Snippets Usage
- **Definition:** Use the `#snippet` syntax to define reusable markup sections.
- **Basic Example:**
```javascript
{#snippet figure(image)}
<figure>
<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
<figcaption>{image.caption}</figcaption>
</figure>
{/snippet}
```
- **Invocation:** Render predefined snippets with `@render`:
```javascript
{@render figure(image)}
```
- **Destructuring Parameters:** Parameters can be destructured for concise usage:
```javascript
{#snippet figure({ src, caption, width, height })}
<figure>
<img alt={caption} {src} {width} {height} />
<figcaption>{caption}</figcaption>
</figure>
{/snippet}
```
### Snippet Scope
- **Scope Rules:** Snippets have lexical scoping rules; they are visible to everything in the same lexical scope:
```javascript
<div>
{#snippet x()}
{#snippet y()}...{/snippet}
<!-- valid usage -->
{@render y()}
{/snippet}
<!-- invalid usage -->
{@render y()}
</div>
<!-- invalid usage -->
{@render x()}
```
- **Recursive References:** Snippets can self-reference or reference other snippets:
```javascript
{#snippet blastoff()}
<span>🚀</span>
{/snippet}
{#snippet countdown(n)}
{#if n > 0}
<span>{n}...</span>
{@render countdown(n - 1)}
{:else}
{@render blastoff()}
{/if}
{/snippet}
{@render countdown(10)}
```
#### Passing Snippets to Components
- **Direct Passing as Props:**
```javascript
<script>
import Table from './Table.svelte';
const fruits = [{ name: 'apples', qty: 5, price: 2 }, ...];
</script>
{#snippet header()}
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</th>
{/snippet}
{#snippet row(fruit)}
<td>{fruit.name}</td>
<td>{fruit.qty}</td>
<td>{fruit.price}</td>
<td>{fruit.qty * fruit.price}</td>
{/snippet}
<Table data={fruits} {header} {row} />
```
- **Implicit Binding:**
```html
<table data="{fruits}">
{#snippet header()}
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</th>
{/snippet} {#snippet row(fruit)}
<td>{fruit.name}</td>
<td>{fruit.qty}</td>
<td>{fruit.price}</td>
<td>{fruit.qty * fruit.price}</td>
{/snippet}
</table>
```
- **Children Snippet:** Non-snippet content defaults to the `children` snippet:
```html
<table data="{fruits}">
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</th>
<!-- additional content -->
</table>
<script>
let { data, children, row } = $props();
</script>
<table>
<thead>
<tr>
{@render children()}
</tr>
</thead>
<!-- table body -->
</table>
```
- **Avoid Conflicts:** Do not use a prop named `children` if also providing content inside the component.
### Typing Snippets
- **TypeScript Integration:**
```typescript
<script lang="ts">
import type { Snippet } from 'svelte';
let { data, children, row }: {
data: any[];
children: Snippet;
row: Snippet<[any]>;
} = $props();
</script>
```
- **Generics for Improved Typing:**
```typescript
<script lang="ts" generics="T">
import type { Snippet } from 'svelte';
let { data, children, row }: {
data: T[];
children: Snippet;
row: Snippet<[T]>;
} = $props();
</script>
```
## Creating Snippets Programmatically
- **Advanced Use:** Create snippets programmatically using `createRawSnippet` where necessary.
## Snippets and Slots
- **Mixing with Slots:** Slots are deprecated but still work. Snippets provide more flexibility and power.
- **Custom Elements:** Continue using `<slot />` for custom elements as usual.
## Event Handlers in Svelte 5
### Overview of Event Handlers
In Svelte 5, event handlers are treated as properties, simplifying their use and integrating them more closely with the rest of the properties in the component.
### Basic Event Handlers
- **Declaration:** Use properties to attach event handlers.
```javascript
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
clicks: {count}
</button>
```
- **Shorthand Syntax:**
```javascript
<script>
let count = $state(0);
function handleClick() {
count++;
}
</script>
<button {handleClick}>
clicks: {count}
</button>
```
- **Deprecation:** The traditional `on:` directive is deprecated.
### Component Events
- **Replacing createEventDispatcher:** Components should accept callback props instead of using `createEventDispatcher`.
```javascript
<script>
import Pump from './Pump.svelte';
let size = $state(15);
let burst = $state(false);
function reset() {
size = 15;
burst = false;
}
</script>
<Pump
inflate={(power) => { size += power; if (size > 75) burst = true; }}
deflate={(power) => { if (size > 0) size -= power; }}
/>
{#if burst}
<button onclick={reset}>new balloon</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}"> 🎈 </span>
{/if}
```
### Bubbling Events
- **Accept Callback Props:**
```javascript
<script>
let { onclick, children } = $props();
</script>
<button {onclick}>
{@render children()}
</button>
```
- **Spreading Props:**
```javascript
<script>
let { children, ...props } = $props();
</script>
<button {...props}>
{@render children()}
</button>
```
### Event Modifiers
- **Avoiding Modifiers:** Modifiers like `|once`, `|preventDefault`, etc., are not supported. Use wrapper functions instead.
- **Example Wrapper Functions:**
```javascript
<script>
function once(fn) {
return function (event) {
if (fn) fn.call(this, event);
fn = null;
};
}
function preventDefault(fn) {
return function (event) {
event.preventDefault();
fn.call(this, event);
};
}
</script>
<button onclick={once(preventDefault(handler))}>...</button>
```
- **Special Modifiers:** For `capture`:
```javascript
<button onclickcapture={...}>...</button>
```
### Multiple Event Handlers
- **Combining Handlers:** Instead of using multiple handlers, combine them into one.
```javascript
<button
onclick={(e) => {
handlerOne(e);
handlerTwo(e);
}}
>
...
</button>
```
## Examples: Old vs New
### Counter Example
- **Svelte 4 vs. Svelte 5:**
- **Before:**
```html
<script>
let count = 0;
$: double = count * 2;
$: {
if (count > 10) alert('Too high!');
}
</script>
<button on:click="{()" ="">count++}> {count} / {double}</button>
```
- **After:**
```html
<script>
let count = $state(0);
let double = $derived(count * 2);
$effect(() => {
if (count > 10) alert('Too high!');
});
</script>
<button onclick="{()" ="">count++}> {count} / {double}</button>
```
#### Tracking Dependencies
- **Svelte 4 vs. Svelte 5:**
- **Before:**
```html
<script>
let a = 0;
let b = 0;
$: sum = add(a, b);
function add(x, y) {
return x + y;
}
</script>
<button on:click="{()" ="">a++}>a++</button>
<button on:click="{()" ="">b++}>b++</button>
<p>{a} + {b} = {sum}</p>
```
- **After:**
```html
<script>
let a = $state(0);
let b = $state(0);
let sum = $derived(add());
function add() {
return a + b;
}
</script>
<button onclick="{()" ="">a++}>a++</button>
<button onclick="{()" ="">b++}>b++</button>
<p>{a} + {b} = {sum}</p>
```
#### Untracking Dependencies
- **Svelte 4 vs. Svelte 5:**
- **Before:**
```html
<script>
let a = 0;
let b = 0;
$: sum = a + noTrack(b);
function noTrack(value) {
return value;
}
</script>
<button on:click="{()" ="">a++}>a++</button>
<button on:click="{()" ="">b++}>b++</button>
<p>{a} + {b} = {sum}</p>
```
- **After:**
```html
<script>
import { untrack } from 'svelte';
let a = $state(0);
let b = $state(0);
let sum = $derived(add());
function add() {
return a + untrack(() => b);
}
</script>
<button onclick="{()" ="">a++}>a++</button>
<button onclick="{()" ="">b++}>b++</button>
<p>{a} + {b} = {sum}</p>
```
#### Simple Component Props
- **Svelte 5:**
```html
<script>
let { count = 0 } = $props();
</script>
{count}
```
#### Advanced Component Props
- **Svelte 5:**
```html
<script>
let { class: classname, ...others } = $props();
</script>
<pre class="{classname}">
{JSON.stringify(others)}
</pre>
```
#### Autoscroll Example
- **Svelte 4 vs. Svelte 5:**
- **Before:**
```html
<script>
import { tick, beforeUpdate } from 'svelte';
let theme = 'dark';
let messages = [];
let viewport;
let updatingMessages = false;
beforeUpdate(() => {
if (updatingMessages) {
const autoscroll =
viewport &&
viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50;
if (autoscroll) {
tick().then(() => viewport.scrollTo(0, viewport.scrollHeight));
}
}
});
function handleKeydown(event) {
if (event.key === 'Enter') {
const text = event.target.value;
if (text) {
messages = [...messages, text];
updatingMessages = true;
event.target.value = '';
}
}
}
function toggle() {
theme = theme === 'dark' ? 'light' : 'dark';
}
</script>
<div class:dark="{theme" ="" ="" ="dark" }>
<div bind:this="{viewport}">
{#each messages as message}
<p>{message}</p>
{/each}
</div>
<input on:keydown="{handleKeydown}" />
<button on:click="{toggle}">Toggle dark mode</button>
</div>
```
- **After:**
```html
<script>
import { tick } from 'svelte';
let theme = $state('dark');
let messages = $state([]);
let viewport;
$effect.pre(() => {
messages;
const autoscroll =
viewport && viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50;
if (autoscroll) {
tick().then(() => viewport.scrollTo(0, viewport.scrollHeight));
}
});
function handleKeydown(event) {
if (event.key === 'Enter') {
const text = event.target.value;
if (text) {
messages = [...messages, text];
event.target.value = '';
}
}
}
function toggle() {
theme = theme === 'dark' ? 'light' : 'dark';
}
</script>
<div class:dark="{theme" ="" ="" ="dark" }>
<div bind:this="{viewport}">
{#each messages as message}
<p>{message}</p>
{/each}
</div>
<input onkeydown="{handleKeydown}" />
<button onclick="{toggle}">Toggle dark mode</button>
</div>
```
#### Forwarding Events
- **Svelte 5:**
```html
<script>
let { ...props } = $props();
</script>
<button {...props}>a button</button>
```
#### Passing UI Content to a Component
- **Passing content using snippets:**
```html
<!-- consumer -->
<script>
import Button from './Button.svelte';
</script>
<button>{#snippet children(prop)} click {prop} {/snippet}</button>
<!-- provider (Button.svelte) -->
<script>
let { children } = $props();
</script>
<button>{@render children("some value")}</button>
```
### SvelteKit 2 Changes
#### Redirect and Error Handling
In SvelteKit 2, it is no longer necessary to throw the results of `error(...)` and `redirect(...)`. Simply calling them is sufficient.
**SvelteKit 1:**
```javascript
import { error } from '@sveltejs/kit';
function load() {
throw error(500, 'something went wrong');
}
```
**SvelteKit 2:**
```javascript
import { error } from '@sveltejs/kit';
function load() {
error(500, 'something went wrong');
}
```
**Distinguish Errors:**
Use `isHttpError` and `isRedirect` to differentiate known errors from unexpected ones.
```javascript
import { isHttpError, isRedirect } from '@sveltejs/kit';
try {
// some code
} catch (err) {
if (isHttpError(err) || isRedirect(err)) {
// handle error
}
}
```
#### Cookie Path Requirement
Cookies now require a specified path when set, deleted, or serialized.
**SvelteKit 1:**
```javascript
export function load({ cookies }) {
cookies.set(name, value);
return { response };
}
```
**SvelteKit 2:**
```javascript
export function load({ cookies }) {
cookies.set(name, value, { path: '/' });
return { response };
}
```
#### Top-Level Promise Handling
Promises in `load` functions are no longer awaited automatically.
**Single Promise:**
**SvelteKit 1:**
```javascript
export function load({ fetch }) {
return {
response: fetch(...).then(r => r.json())
};
}
```
**SvelteKit 2:**
```javascript
export async function load({ fetch }) {
const response = await fetch(...).then(r => r.json());
return { response };
}
```
**Multiple Promises:**
**SvelteKit 1:**
```javascript
export function load({ fetch }) {
return {
a: fetch(...).then(r => r.json()),
b: fetch(...).then(r => r.json())
};
}
```
**SvelteKit 2:**
```javascript
export async function load({ fetch }) {
const [a, b] = await Promise.all([
fetch(...).then(r => r.json()),
fetch(...).then(r => r.json())
]);
return { a, b };
}
```
#### `goto` Changes
`goto(...)` no longer accepts external URLs. Use `window.location.href = url` for external navigation.
#### Relative Paths Default
Paths are now relative by default, ensuring portability across different environments. The `paths.relative` config option manages this behavior.
#### Deprecated Settings and Functions
- **Server Fetches** are no longer trackable.
- **`preloadCode` Arguments:** Must be prefixed with the base path.
- **`resolvePath` Replacement:** Use `resolveRoute` instead.
```javascript
import { resolveRoute } from '$app/paths';
const path = resolveRoute('/blog/[slug]', { slug: 'hello' });
```
#### Improved Error Handling
Errors trigger the `handleError` hook with `status` and `message` properties for better discernment.
#### Dynamic Environment Variables
Dynamic environment variables cannot be used during prerendering. Use static modules instead.
#### `use:enhance` Callback Changes
The properties `form` and `data` have been removed from `use:enhance` callbacks, replaced by `formElement` and `formData`.
#### Forms with File Inputs
Forms containing `<input type="file">` must use `enctype="multipart/form-data"`.
With these adjusted guidelines, your AI can now generate SvelteKit 2 code accurately while considering the migration changes.
### Typescript & Syntax
- Strict mode. Avoid `any`.
- Use optional chaining, union types (no enums).
Comprehensive .cursorrules file for Next.js 15 App Router projects with TypeScript, enforcing server components by default, proper use of "use client" directive, and App Router conventions.
Cursor rules for Python FastAPI projects enforcing async patterns, Pydantic v2 models, dependency injection, and proper error handling.
Rules for consistent React component development with TypeScript interfaces, proper hook patterns, and component composition.
Rules optimizing Cursor Agent mode behavior including multi-file editing context, session management, and autonomous task completion patterns.
Cursor rules for projects using Tailwind CSS with shadcn/ui component library, enforcing consistent utility class usage and component patterns.
Rules for Go backend services enforcing idiomatic Go patterns, proper error handling, and clean architecture conventions.