Reviews Svelte components for idiomatic patterns, identifies anti-patterns carried over from React/Vue/Svelte 4, and suggests Svelte 5 refactorings. Use when writing or reviewing Svelte components, asking "is this idiomatic", "Svelte way", doing Svelte 5 migration, or encountering smells like: effect assigns state, prop copied to $state, global store by default, $bindable everywhere, clickable div, createEventDispatcher, export let, on:click, slot-shaped APIs, lifecycle-driven code, imperative DOM wiring, immutable-update ceremony, context value replacement, shadcn-svelte form structure, Field.* components, bits-ui form controls, or component APIs that hide ownership. This is the general-purpose entry point for Svelte component review; delegates to sveltekit for routes/load/actions/server concerns and to focused references for details.
100
100%
Does it follow best practices?
Impact
100%
1.12xAverage score across 3 eval scenarios
Passed
No findings from the security scan
You already know Svelte syntax. This skill changes your defaults — what you reach for first when designing a component, placing state, modeling reactivity, and reviewing UI code.
The core failure mode is Svelte that compiles but thinks like React, Vue, or Svelte 4 — effects standing in for derived values and events, props mirrored into state, global stores by reflex, two-way binding as the default, clickable divs, Svelte 4 component APIs in new code.
The component is a small reactive program with visible dependencies. Keep dataflow direct, state ownership obvious, and markup semantic; let the compiler and the DOM do the work.
This skill is the general-purpose entry point for Svelte component review; for SvelteKit-level concerns, also use sveltekit (see Cross-References).
Treat these as strong defaults, not rigid laws: when unsure, choose the approach that makes ownership, dependencies, and user semantics more explicit.
1. Reactivity is read-tracked. A $derived/$effect depends on what it reads while running; if one reruns unexpectedly, inspect its reads, not a dependency array. See references/read-tracked-reactivity.md.
2. Derived state is $derived, not $effect. Pure functions of other state get derived; effects are for side effects, not keeping variables in sync. See references/effect-driven-state.md.
<!-- ❌ effect writing derived state -->
let doubled = $state(0);
$effect(() => { doubled = count * 2; });
<!-- ✅ derive it -->
let doubled = $derived(count * 2);3. Effects are escape hatches. Before $effect, check for a derived value, an event handler, or a bind: getter/setter. Use it only to touch the outside world — subscriptions, timers, observers, imperative APIs, logging — and return cleanup.
4. Runes are statically analyzable. Compiler syntax, not runtime hooks — reusable state becomes .svelte.ts classes and factories, not custom hooks. See references/static-runes.md.
5. $state is a deep proxy, not React state. Mutate what changed in local arrays and objects; skip the immutable-update ceremony. See references/deep-state-without-immutable-ceremony.md.
6. State has an owner. Local interaction state → component; reusable behavior → .svelte.ts class; descendant-shared → context; stores only for the store contract, not every changing value. See references/state-ownership.md.
7. Do not mirror props unless you mean "draft." Copying props into $state creates two sources of truth — derive from props, or make a named editable draft with reset/commit. See references/prop-mirroring.md.
<!-- ❌ prop mirrored into local state -->
let { value } = $props();
let local = $state(value); // stale if parent changes
<!-- ✅ derive from prop -->
let { value } = $props();
let display = $derived(value.toUpperCase());8. Context shares identity, not provider rerenders. Put a reactive object/class in context and mutate its properties; replacing plain values won't behave like React Provider updates. See references/context-identity.md.
9. Module-level mutable state is suspicious in SSR apps. In SvelteKit, exported mutable instances leak between requests — use factories, context, or request-owned data. See references/ssr-state-leaks.md.
10. Props describe inputs; callbacks describe events. Component events are callback props; avoid createEventDispatcher in new code unless preserving an old public API. See references/component-patterns.md.
<!-- ❌ createEventDispatcher in Svelte 5 -->
const dispatch = createEventDispatcher();
function handleClick() { dispatch('select', item); }
<!-- ✅ callback prop -->
let { onSelect } = $props();
function handleClick() { onSelect?.(item); }11. Two-way binding is an API commitment. $bindable means the child may mutate parent-owned state — use it for form controls and deliberate controlled components, not as a default. See references/bindable-by-default.md.
12. Snippets are render functions, not renamed slots. Use them when the parent supplies UI; type their parameters and name them by role. See references/snippets-as-render-functions.md.
13. Wrapper components should preserve the platform. Wrapping a button means accepting button attributes and keeping type, disabled, focus, and ARIA intact. See references/wrapper-components-preserve-html.md.
14. Native elements before custom behavior. A button already handles keyboard, focus, disabled, and a11y; a clickable div rebuilds the platform badly. See references/semantic-html-first.md.
15. Design-system fields should preserve form semantics. In shadcn-svelte or bits-ui forms, use Field.* for labels, groups, descriptions, and errors — not raw divs, separate Label imports, or clickable option rows. See references/shadcn-svelte-forms.md.
16. Components own markup; actions/attachments own DOM behavior. Renders UI → component. Attaches reusable imperative behavior to an element → action or attachment. See references/actions-vs-components.md.
17. Svelte 5 syntax should be consistent. New code uses $props, onclick-style attributes, snippets, and callback props; don't mix old syntax outside legacy maintenance. See references/svelte5-syntax-discipline.md.
18. Type the boundary, not every breath. Non-trivial components get a Props type; type snippets and callbacks; use svelte/elements for wrappers; avoid broad any at boundaries. See references/typescript.md.
package.json for the svelte major and check svelte.config.*. In a Svelte 5 project, flag Svelte 4 patterns (export let, on:event, <slot>, createEventDispatcher); in a Svelte 4 project, do not recommend Svelte 5-only syntax unless the task is migration.{#each}, move heavy work out of markup into $derived, avoid needless two-way bindings. Most code needs no manual optimization.Scan for the smell; the principle above carries the fix.
| Code smell | Reference |
|---|---|
| Old Svelte syntax in new code | svelte5-syntax-discipline |
| Effect assigns a derived value | effect-driven-state |
| Effect responds to a control changing | effect-driven-state |
| Effect converts a bound value | bindings-and-directives |
| Effect reruns mysteriously | read-tracked-reactivity |
| Rune called inside a helper or hook | static-runes |
| Immutable-update ceremony on local state | deep-state-without-immutable-ceremony |
| Prop copied into local state | prop-mirroring |
| Global store by default / owner unclear | state-ownership |
| Context value replaced instead of mutated | context-identity |
| Exported mutable singleton in SvelteKit | ssr-state-leaks |
createEventDispatcher to emit an event | component-patterns |
$bindable by habit | bindable-by-default |
Slot-shaped API (export let, <slot>) | snippets-as-render-functions |
Clickable div / span | semantic-html-first |
| shadcn-svelte form rebuilt with raw wrappers | shadcn-svelte-forms |
| Wrapper that hides native behavior | wrapper-components-preserve-html |
| Action that owns markup/state | actions-vs-components |
| Dynamic list left unkeyed | bindings-and-directives |
Broad any at the component boundary | typescript |
{@html} with user content | template-tags |
| Component behavior left untested | testing |
load, form actions, remote functions, cookies, auth, endpoints, redirects, server errors, app-level SSR/data flowsvelte5/SKILL.md
516dee7
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.