From harness-claude
Declares reactive state ($state), derived values ($derived), side effects ($effect), and props ($props, $bindable) in Svelte 5 components. Useful for new code or migrating from Svelte 4 reactivity.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Declare reactive state, derived values, and side effects in Svelte 5 using the runes API ($state, $derived, $effect, $props, $bindable)
Guides Svelte 5 runes for reactive state ($state), derived values ($derived), effects ($effect), props ($props/$bindable), migration from Svelte 4, and common mistake prevention.
Provides examples for Svelte 5 runes ($state, $derived), bindable props, snippets, SvelteKit load functions and form actions, plus Svelte 4 migration guidance.
Guides Svelte 5 best practices for reactivity using $state, $derived, $effect, $props; covers event handling, styling, library integration. For writing/editing Svelte components.
Share bugs, ideas, or general feedback.
Declare reactive state, derived values, and side effects in Svelte 5 using the runes API ($state, $derived, $effect, $props, $bindable)
let x declarations and $: reactive statements$state — reactive local state:
$state. Unlike Svelte 4's magic let, $state is explicit and works anywhere (not just .svelte files):<script lang="ts">
let count = $state(0)
let items = $state<string[]>([])
let user = $state<User | null>(null)
</script>
<button onclick={() => count++}>{count}</button>
$state are deeply reactive — mutations are tracked automatically:<script lang="ts">
let todos = $state([
{ id: 1, text: 'Buy milk', done: false }
])
function toggle(id: number) {
const todo = todos.find(t => t.id === id)
if (todo) todo.done = !todo.done // mutation tracked
}
</script>
$state.raw for large non-reactive objects that should only update on full reassignment:let data = $state.raw<HeavyObject>(initialData);
// later:
data = newData; // triggers update; internal mutations do not
$derived — computed values:
$: computed = expr with $derived:<script lang="ts">
let count = $state(0)
let doubled = $derived(count * 2)
let isEven = $derived(count % 2 === 0)
</script>
$derived.by for multi-line derived logic:let filteredItems = $derived.by(() => {
return items.filter((item) => item.active).sort((a, b) => a.name.localeCompare(b.name));
});
$effect — side effects:
$: { sideEffect() } and onMount with $effect. It runs after DOM updates, re-runs when dependencies change, and cleans up on re-run or destroy:$effect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') closeModal();
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler); // cleanup
});
$effect does NOT run on the server. For SSR-compatible initialization, use onMount from svelte:import { onMount } from 'svelte';
onMount(() => {
initMap();
});
$props — component props:
$props() — it replaces export let declarations:<script lang="ts">
interface Props {
name: string
age?: number
onSelect?: (value: string) => void
}
let { name, age = 18, onSelect }: Props = $props()
</script>
<script lang="ts">
let { class: className, ...rest }: { class?: string } & Record<string, unknown> = $props()
</script>
<div class={className} {...rest} />
$bindable — two-way bound props:
bind:propName:<!-- Child: InputField.svelte -->
<script lang="ts">
let { value = $bindable('') }: { value?: string } = $props()
</script>
<input bind:value />
<!-- Parent: -->
<InputField bind:value={searchQuery} />
Runes vs. Svelte 4 reactivity:
| Svelte 4 | Svelte 5 Rune |
|---|---|
let count = 0 | let count = $state(0) |
$: doubled = count * 2 | let doubled = $derived(count * 2) |
$: { effect() } | $effect(() => { effect() }) |
export let prop | let { prop } = $props() |
export let val = $bindable | let { val = $bindable() } = $props() |
Runes work outside .svelte files:
This is the key architectural difference from Svelte 4. $state, $derived, and $effect work in .svelte.ts and .svelte.js files — enabling reactive shared logic without stores:
// lib/useCounter.svelte.ts
export function useCounter(initial = 0) {
let count = $state(initial);
const doubled = $derived(count * 2);
return {
get count() {
return count;
},
get doubled() {
return doubled;
},
increment: () => count++,
};
}
Getter pattern for exposing $state:
To expose reactive state from a function, return getters (not values) so the reactive signal flows through:
// Do this:
return {
get count() {
return count;
},
};
// Not this (loses reactivity):
return { count };
$effect dependency tracking:
$effect automatically tracks all $state and $derived values read during its execution. You do not declare dependencies manually. If you read a value inside a conditional branch, it only becomes a dependency when that branch is reached.
https://svelte.dev/docs/svelte/what-are-runes