svelte
Create or edit Svelte/SvelteKit files (`*.svelte`, `*.svelte.js`, `+page.svelte`, `+page.js`, `+layout.js`)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Create or edit Svelte/SvelteKit files (`*.svelte`, `*.svelte.js`, `+page.svelte`, `+page.js`, `+layout.js`)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use only when preparing or advising on a commit subject in this repository, especially when choosing web:, server:, client:, protocol:, android:, desktop:, or ci:.
Use only when editing Android code under android, crates/android-native, packages/client/src/native.js, or Android playback, JNI, Gradle, and TWA integration.
Use only when editing Desktop-specific code in packages/web/src-tauri, packages/client/src/desktop.js, or Desktop packaging and tests.
Use only when changing the shared wire contract in crates/protocol or coordinated request/response consumers.
Use only when editing crates/server or its library scanning, indexing, metadata, authorization, or streaming behavior.
Use only when editing browser-facing code in packages/web, packages/client, or crates/web-wasm.
| name | svelte |
| description | Create or edit Svelte/SvelteKit files (`*.svelte`, `*.svelte.js`, `+page.svelte`, `+page.js`, `+layout.js`) |
Apply these rules when creating or modifying Svelte files. Preserve existing project conventions when they are stricter, but do not introduce legacy Svelte patterns.
export let, $:, on:click, or slots.{@render ...}.{const value = $derived(expression)} when the value must update and {let value = $state(initial)} for mutable reactive state. Use bare {const value = expression} only when the value does not need to update, and avoid bare {let ...} for state. Do not use legacy {@const ...}.{@attach ...} over bind:this, onMount, and onDestroy. Reactive reads inside an attachment cause reattachment.$props().{#await ...} blocks as legacy. Use top-level <script> await for component-level work and {await expression} inside <svelte:boundary> for a local subtree. The latter is a markup await, not top-level await. Add a pending snippet only when the expected delay justifies replacing the current UI; omit it for short work to avoid loader flashes.$app/state, not $app/stores.$lib for imports from src/lib.goto from $app/navigation for application navigation instead of assigning window.location.href.const { data } = $props() and route load functions used for anything other than navigation guards or redirects as legacy patterns. Initialize and fetch in components under <svelte:boundary>; initialize the application in +layout.svelte and gate its children with the boundary.$state for mutable state, $derived for expressions, and $derived.by for multi-step calculations. Default to $state([]) for arrays. When an array is only reassigned, $state.raw([]) avoids unnecessary proxy overhead, especially for large arrays. Its elements can still be independently reactive through their own $state fields.$derived with const unless it is reassigned; then use let, $derived.by() cannot be reassigned/mutated.$derived values over one large computation. They stay lazy, track narrower dependency sets, and make broad or expensive invalidations easy to locate and fix.ConnectPage or PageState; keep view-only state and derived values as top-level runes in the component. Put reusable domain classes in .svelte.js and page-local domain classes in the component.push and splice when updating an existing reactive array. Do not replace the entire array solely to trigger reactivity.$effect for synchronization. Use derived state, function bindings, attachments, or direct mutation at the event/API/entity method that owns the change.Use const when code only reads the binding and let when code also assigns to it. Both forms are valid:
const total = $derived(lines.reduce((sum, line) => sum + line.quantity, 0));
let selected = $derived(lines[0]);
function select(line) {
selected = line;
}
selected is still derived state; declaring it with let allows the explicit override which quite often can help with avoiding $effect.
Keep each dependency step narrow and lazy. This exposes where work happens and lets an unchanged intermediate value stop invalidation from reaching later calculations:
const search = $derived(query.toUpperCase());
const visible = $derived(records.filter((record) => record.name.includes(search)));
const groups = $derived(group_by(visible, (record) => record.group));
A boolean derived is a useful gate:
const overweight = $derived(weight > 100);
const warning = $derived(overweight ? x : y);
Changing weight from 110 to 120 keeps overweight true, so warning is not recalculated. Changing it from 120 to 90 changes overweight to false and recalculates warning; further changes below 100 are skipped again.
Values read inside asynchronous callbacks are not tracked automatically. Pass reactive dependencies as function arguments so they are read synchronously by the reactive expression and become stable snapshots inside the callback:
let filter = $state("active");
let limit = $state(20);
function createRequest(filter, limit) {
return observe(async () => {
// Values read inside this callback are not tracked automatically
return await loadItems({ filter, limit });
});
}
const results = $derived(createRequest(filter, limit));
Declaration tags only provide local variables and block scope; $derived and $state provide reactivity. Keep small local values next to the markup that uses them instead of hoisting them into the component script:
<section>
{const template = $derived(compact ? "1fr 5rem" : "1fr 8rem")}
<header style:grid-template-columns={template}>
...
</header>
<article style:grid-template-columns={template}>
...
</article>
</section>
{const template = compact ? a : b} would not update when compact changes; $derived is what makes it reactive. Likewise, {let expanded = false} is not reactive; use {let expanded = $state(false)} when assignments must update the markup. Their scope and lifetime follow the surrounding block.
Use svelte's built-in reactive collections when mutations such as .add(), .set(), or .delete() must update derived values or markup:
<script>
import { SvelteSet } from "svelte/reactivity";
const selected = new SvelteSet();
</script>
<button onclick={() => selected.add(record.id)}>
Select
</button>
{selected.size} selected
Use SvelteMap, SvelteSet, or SvelteURLSearchParams instead of their native counterparts when the collection itself participates in reactivity.
Register DOM behavior and its cleanup in the same attachment. Compose independent behaviors directly on the element:
<script>
function autoselect(element) {
const select = () => element.select();
element.addEventListener("focus", select);
element.addEventListener("click", select);
return () => {
element.removeEventListener("focus", select);
element.removeEventListener("click", select);
};
}
</script>
<input {@attach autoselect} {@attach tooltip("Search")} />
Normalize or coordinate writes at the binding boundary:
<input bind:value={() => search, (value) => (search = value.toUpperCase())} />
Use class accessors for bindings with coordinated writes or side effects. This avoids having to use $effect and leaking wrapper internals such as .current into the template and keeps the markup as a clean domain property:
<script>
class Person {
#name = $state();
get name() {
return this.#name;
}
set name(value) {
this.#name = value;
doSomethingElse();
}
};
const person = new Person();
</script>
<input bind:value={person.name} />
Classes represent concepts in the application domain, not the component containing them. Keep view-only state at the component level and use classes for domain models that own behavior:
<script>
class Order {
name;
lines = $state([]);
total = $derived(this.lines.reduce((sum, line) => sum + line.quantity, 0));
constructor(name) {
this.name = name;
}
add(line) {
this.lines.push(line);
}
}
let search = $state("");
const orders = $state([new Order("First order")]);
const visible = $derived(orders.filter((order) => order.name.includes(search)));
</script>
{#each visible as order}
<p>{order.name}: {order.total}</p>
{/each}
search and visible only shape this view, while Order represents a domain entity and owns its lines and total.
Use SvelteURLSearchParams when search parameters participate in reactivity. Keeping filters and selections in the URL makes the current view shareable: someone opening the link gets the same values already applied.
<script>
import { replaceState } from "$app/navigation";
import { page } from "$app/state";
import { SvelteURLSearchParams } from "svelte/reactivity";
const params = $derived(new SvelteURLSearchParams(page.url.search));
const query = $derived(params.get("query") ?? "");
</script>
<input bind:value={() => query, (value) => {
params.set('query', value);
replaceState(`?${params}`, {});
}}/>
Run application initialization in +layout.svelte and await it inside a boundary before rendering child routes:
<script>
const { children } = $props();
</script>
<svelte:boundary>
{void (await App.initialize())}
{@render children()}
{#snippet pending()}
<p>Starting application…</p>
{/snippet}
{#snippet failed(error)}
<p>{error.message}</p>
{/snippet}
</svelte:boundary>
This keeps startup ordering, loading UI, and initialization errors in the component tree instead of hiding them in a route loader.