svelte-best-practices
Svelte 5 best practices including runes ($state, $derived, $effect), dependency tracking patterns, ESLint configuration, and component patterns
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Svelte 5 best practices including runes ($state, $derived, $effect), dependency tracking patterns, ESLint configuration, and component patterns
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | svelte-best-practices |
| description | Svelte 5 best practices including runes ($state, $derived, $effect), dependency tracking patterns, ESLint configuration, and component patterns |
| user-invocable | false |
Reference for Svelte 5 runes mode. Assumes familiarity with Svelte basics.
$state for values that drive reactivity (effects, derived, template).$state({...}) / $state([...]) gives deep reactivity via proxies. Use $state.raw for large objects that only get reassigned (e.g., API responses) to avoid proxy overhead.Compute values from state with $derived, never with $effect:
// Good
let square = $derived(num * num);
// Bad — don't use effects to compute values
let square;
$effect(() => { square = num * num; });
$derived takes an expression. Use $derived.by(() => ...) for complex logic.$state), but re-evaluate when deps change.Props can change at any time. Anything derived from props needs $derived:
// Correct — updates when type changes
let color = $derived(type === 'danger' ? 'red' : 'green');
// Wrong — color is computed once and never updates
let color = type === 'danger' ? 'red' : 'green';
Debug reactivity issues by adding $inspect.trace(label) as the first line in $effect or $derived.by to trace which dependencies triggered an update.
$effect is an escape hatch. Use it minimally.
| Instead of $effect... | Use this |
|---|---|
| Sync state to external lib (D3) | {@attach ...} |
| Respond to user interaction | Event handler or function binding |
| Compute a value from state | $derived |
| Debug reactive values | $inspect |
| Observe external data | createSubscriber |
Never wrap effect contents in if (browser) {...} — effects already skip SSR.
Svelte tracks dependencies by detecting which reactive values are read during effect execution. There are no explicit dependency arrays — GitHub issues #9248 and #13207 requesting them were closed as "not planned".
Svelte reactivity only tracks its own primitives ($state, $derived, $props). DOM properties like scrollHeight, offsetWidth are plain reads — NOT tracked. If you need to react to DOM changes caused by state changes, depend on the state, not the DOM property.
When an effect needs specific triggers but its logic reads other reactive values or DOM properties, use void to declare dependencies and untrack() to isolate logic:
<script>
import { untrack } from 'svelte';
let { messages, streamingText, pendingPermissions } = $props();
let container;
$effect.pre(() => {
// Declare dependencies — void evaluates (registers with tracker) and discards
void messages.length;
void streamingText;
void pendingPermissions;
untrack(() => {
// Logic reads DOM properties freely without registering them as deps
const threshold = 200;
if (container.scrollHeight - container.scrollTop < threshold) {
container.scrollTo(0, container.scrollHeight);
}
});
});
</script>
messages.length; statements in $effect.pre to declare dependencies.void val; is the lint-safe version — identical runtime behavior, no ESLint warnings (except sonarjs/void-use, see ESLint section).untrack(fn) for logic.$effect.pre runs before DOM updates (equivalent to beforeUpdate). Use for scroll management, DOM measurement before paint.$effect runs after DOM updates.field-sizing: content — Pure CSS, no JS. ~80% browser support (not Firefox).oninput handler — Idiomatic Svelte 5, no $effect. Only handles user input.$effect with natural read — Needed when text changes programmatically.<pre> mirror — Official Svelte playground approach, no JS height calc.use:autosize) — Reusable across textareas.<!-- Standard -->
<button onclick={() => doThing()}>click</button>
<!-- Shorthand -->
<button {onclick}>click</button>
<!-- Spread -->
<button {...props}>click</button>
<!-- Window/document events — don't use onMount/$effect for these -->
<svelte:window onkeydown={handleKey} />
<svelte:document onvisibilitychange={handleVisibility} />
Reusable markup chunks, replacing slots:
{#snippet greeting(name)}
<p>hello {name}!</p>
{/snippet}
{@render greeting('world')}
<script>.<script module> and can be exported.Always use keyed each blocks. Keys must uniquely identify items — never use indices:
{#each items as item (item.id)}
<Item {item} />
{/each}
Skip destructuring when mutating items (e.g., bind:value={item.count}).
<div style:--columns={columns}>...</div>
<style>
div { grid-template-columns: repeat(var(--columns), 1fr); }
</style>
Prefer CSS custom properties. Fall back to :global only when necessary:
<!-- Parent -->
<Child --color="red" />
<!-- Child -->
<style>
h1 { color: var(--color); }
</style>
<!-- Override when custom properties aren't an option -->
<div>
<Child />
</div>
<style>
div :global {
h1 { color: red; }
}
</style>
Prefer context over shared module state. Module-level state leaks between users during SSR.
Use createContext over setContext/getContext for type safety.
The void dependency pattern is canonical Svelte 5. Disable this rule for .svelte files:
// eslint.config.js
{
files: ['**/*.svelte'],
rules: {
'sonarjs/void-use': 'off',
},
}
SvelteKit virtual modules need to be ignored:
{
rules: {
'import-x/no-unresolved': ['error', {
ignore: ['^\\$app/', '^\\$env/', '^\\$service-worker']
}],
},
}
Svelte re-exports from svelte/transition, svelte/easing, etc. may trigger false warnings. Suppress per-case if needed.
| Legacy | Svelte 5 Replacement |
|---|---|
let count = 0 (implicit reactivity) | $state |
$: statements | $derived / $effect |
export let / $$props / $$restProps | $props |
on:click={...} | onclick={...} |
<slot> / $$slots / <svelte:fragment> | {#snippet} / {@render} |
<svelte:component this={...}> | <DynamicComponent> |
<svelte:self> | Direct self-import |
| Stores | Classes with $state fields |
use:action | {@attach ...} |
class: directive | class with clsx-style arrays/objects |
Svelte 5.36+ supports await expressions in components. Requires experimental.async in svelte.config.js. Not stable — use cautiously.
Access macOS Messages and Contacts programmatically without Full Disk Access. Use when: (1) "Operation not permitted" error accessing ~/Library/Messages/chat.db or ~/Library/Application Support/AddressBook/, (2) Need to read iMessage history, contacts, or chat data from terminal/scripts, (3) Need to SAVE or update a contact (name, phone, email, Instagram/social handle, note), (4) CNContactStore/CNSaveRequest write fails with a CoreData 4097 XPC error, (5) Full Disk Access isn't granted or isn't desirable. Covers AppleScript automation permissions workaround, the read-vs-write permission split, and Beeper Desktop CLI as alternatives to direct database access.
Use compiled Swift binaries instead of AppleScript for macOS system integrations. Use when: (1) an AppleScript is slow (>2s) for Contacts, Calendar, Reminders, or other system data, (2) need to read/write macOS Contacts, Calendar, Reminders, Focus status, or Notifications programmatically, (3) osascript is timing out or iterating slowly, (4) building a new macOS system integration for an AI agent or CLI tool. Swift + native frameworks (CNContactStore, EventKit, UNUserNotificationCenter) are ~100x faster than AppleScript equivalents. Compile once with swiftc, call from Python/Node/Bash.
Use when asked to explain, describe, or write up a topic for a technically competent audience that lacks project-specific or insider context — onboarding a new teammate, an outside reviewer, external docs, or when the user says "explain to someone unfamiliar", "don't assume inside knowledge", or "assume no context".
Git commit best practices for Claude Code. Use when: (1) about to create a git commit, (2) user asks to commit changes, (3) bundling multiple changes together. Covers atomic commits, meaningful messages, what not to commit, and ensuring commit messages reflect all changes in the diff.
Stack Overflow terminal interface with Startpage, StackExchange API, Google, and DuckDuckGo backends. Use when: (1) need to look up a programming question on Stack Overflow, (2) searching StackExchange sites (unix, superuser, serverfault), (3) fetching SO answers programmatically from a script or agent context. Default engine: Startpage. Use --print for non-TTY/pipeable output.
X/Twitter CLI for reading timelines, searching tweets, checking engagement, streaming, and managing lists. Use when: (1) user asks about tweets, Twitter, or X, (2) checking mentions or engagement, (3) searching Twitter discussions, (4) streaming tweets by keyword, (5) looking up users or followers.