Svelte expert
Framework & language expert skills for Claude Code — idiomatic best practices for TypeScript, React, Vue, Svelte, Solid, Angular, Astro
npx -y skills add Akayashuu/agent-skills --skill svelte-expertAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when writing, reviewing, or refactoring Svelte 5 components with runes — reaching for $state, $derived, $effect, $props, $bindable, snippets, stores, or shared reactive state in SvelteKit, or migrating legacy $:/export let code.
SKILL.md
5.9 KB, as published. Nobody here has run it
Svelte Expert
Overview
Svelte 5 replaces compiler-magic reactivity with explicit runes. The mental model is closer to fine-grained signals than to Svelte 4. Most "outdated" code uses $:, export let, and writable stores for local state — all now legacy. Reach for $effect rarely: it is for side effects, not for computing values (same smell as misused React effects).
Quick Reference
| Goal | Do | Avoid |
|---|---|---|
| Reactive local state | let x = $state(0) | top-level let (no longer reactive) |
| Computed value | $derived(a + b) / $derived.by(() => …) | $: reactive statements |
| Component inputs | let { x } = $props() + TS type | export let x |
| Two-way binding | let { v = $bindable() } = $props() | manual event dispatch round-trips |
| Side effect / sync external | $effect(() => …) | using it to derive state |
| Effect cleanup | return () => teardown() | leaking listeners/timers |
| Shared reactive state | rune state in .svelte.ts module | writable store for everything |
| Cross-cutting / RxJS interop | writable/readable store | reinventing with runes |
| List rendering | {#each items as it (it.id)} | unkeyed {#each} |
| Reusable markup | {#snippet} / {@render} | slots (legacy) |
Core Patterns
State + derived — never compute with $effect:
<script lang="ts">
// ❌ legacy + effect-as-derivation: extra render, stale risk
// export let items: Item[];
// $: total = items.reduce((n, i) => n + i.price, 0);
// let count = 0;
// $effect(() => { count = items.length; }); // wrong: derive, don't sync
// ✅ runes
let { items }: { items: Item[] } = $props();
const total = $derived(items.reduce((n, i) => n + i.price, 0));
const count = $derived(items.length);
</script>
Runnable (state factory +
$derived/$derived.by):examples/state-derived.svelte.ts
$props with types and $bindable — replaces export let:
<script lang="ts">
let {
value = $bindable(''), // two-way: <Input bind:value/>
placeholder = 'Search', // plain prop with default
onsubmit, // callback prop, not createEventDispatcher
}: { value?: string; placeholder?: string; onsubmit?: (v: string) => void } = $props();
</script>
$effect only for external systems, with cleanup:
<script lang="ts">
let { roomId }: { roomId: string } = $props();
let messages = $state<Msg[]>([]);
$effect(() => {
const sock = connect(roomId); // re-runs when roomId changes
sock.on('msg', (m) => messages.push(m)); // deep proxy: mutate, don't reassign
return () => sock.close(); // cleanup before re-run / unmount
});
</script>
Use $effect.pre(() => …) (same API, runs before DOM updates) for the rare case of reading
layout before a render — e.g. capturing scroll position for autoscroll.
Runnable (
$effectcleanup +$effect.pre):examples/effect-cleanup.svelte.ts
Shared state — .svelte.ts module, not a store:
// counter.svelte.ts
let count = $state(0); // private: can't export a reassigned $state directly
export const counter = {
get value() { return count; }, // getter exposes the live reactive read
inc() { count++; },
};
// any component: import { counter } — reactive across the app
The compiler wraps reactivity per file, so a directly exported primitive $state won't
stay reactive when reassigned elsewhere. Export an object (mutate its fields) or a
getter/function API. See shared.svelte.ts for both patterns.
Fine-grained reactivity: $state returns a deep proxy. Mutate in place (obj.field = x, arr.push(...)) — reassigning the whole object is unnecessary and loses fine-grained tracking. Use (item.id) keys in {#each} so Svelte moves DOM nodes instead of recreating and remounting them.
Common Mistakes
$:/export letin new code — Svelte 4 idioms; use$derived/$props.$effectto derive state — causes extra passes and stale values; use$derived/$derived.by.- Effect-driven loops — an
$effectthat reads and writes the same$stateloops infinitely. Prefer$derived; only if you must write state in an effect, read the looping dependency viauntrack(() => …). Runnable:examples/derived-not-effect.svelte.ts. - Writable store for component-local state — use
$state; reserve stores for cross-cutting concerns or external (RxJS) interop. - Reassigning whole objects — breaks fine-grained updates; mutate proxy fields directly.
- Unkeyed
{#each}— wrong DOM reuse on reorder; always provide(item.id). createEventDispatcher/ slots — replaced by callback props and snippets.
When NOT to over-engineer
A small leaf component with one prop and no derived state needs no ceremony — let { label } = $props() is the whole story. Don't build a .svelte.ts store for state one component owns. Don't wrap $derived around a value used once inline. Runes are cheap; reach for shared modules and $effect only when something actually crosses component boundaries or touches the outside world.
Sources
- Runes —
$state(incl. cross-module sharing gotcha) $derived/$derived.by$effect/$effect.pre("when not to use",untrack)$props/$bindable- Snippets —
{#snippet}/{@render}(slots deprecated)