Svelte 5 / SvelteKit Code Generation Rules
Loaded when: package.json contains svelte or @sveltejs/kit.
These rules are ADDITIVE to the universal rules in SKILL.md.
CRITICAL: Use Svelte 5 runes syntax ONLY. Never emit Svelte 4 patterns.
Project conventions override: If design-tokens.json contains a conventions section, those conventions take priority over the patterns in this file for stylistic decisions (export style, file naming, import ordering, barrel exports). This file remains authoritative for framework-specific syntax requirements (Svelte 5 runes, $props(), $state(), {#each}, class attribute).
0. NON-NEGOTIABLES
These Svelte 5 / SvelteKit rules hold for every generated file. No exceptions.
- MUST use Svelte 5 runes syntax ONLY. NEVER emit Svelte 4 patterns (
export let,$:,on:click,<slot>). If the project is on Svelte 4 or below, STOP AND ASK the user before proceeding — this file assumes Svelte 5. - MUST use
$props()for props,$state()for state,$derived()for computed,$effect()for side-effects only. NEVER set state inside$effect— use$derivedor$derived.byinstead. - Shared state files MUST use the
.svelte.tsextension, not plain.ts. Runes do not work outside a Svelte module context without that extension. - MUST use
class, NEVERclassName. Svelte uses the native HTMLclassattribute. - MUST use
{@render children()}and{#snippet}for slots. NEVER emit<slot />— it is Svelte 4 syntax.
1. KEY REMINDERS
- Use Svelte 5 runes ONLY. NEVER emit Svelte 4 patterns (
export let,$:,on:click,<slot>). - Use
$props()for props,$state()for state,$derived()for computed,$effect()for side-effects only. - NEVER set state inside
$effect-- use$derivedor$derived.byinstead. - Shared state files MUST use
.svelte.tsextension (not plain.ts) for runes to work outside components. - Use
class, neverclassName. Events useonclick(NOTon:click). Slots use{#snippet}+{@render}(NOT<slot>).
2. SYNTAX QUICK REFERENCE (framework-specific gotchas and newer APIs only)
| Concept | Svelte 5 Syntax |
|---|---|
| Raw state | let data = $state.raw<User[]>([]) -- no deep proxy, reassign-only, use for large API data |
| Computed (fn) | let sorted = $derived.by(() => { return items.toSorted(cmp) }) -- for multi-statement derivations |
| Snippets | {#snippet name(params)}...{/snippet} + {@render name(args)} |
| Shared state | Reactive class in .svelte.ts file: class State { count = $state(0); doubled = $derived(this.count * 2); } |
NEVER use: export let, $:, on:click, <slot>, svelte/store for new code.
3. FILE STRUCTURE CONVENTIONS
| Type | Path |
|---|---|
| Page | src/routes/dashboard/+page.svelte |
| Layout | src/routes/+layout.svelte |
| Server load | src/routes/dashboard/+page.server.ts |
| Universal load | src/routes/dashboard/+page.ts |
| Reusable component | src/lib/components/ComponentName.svelte |
| API route | src/routes/api/users/+server.ts |
| Server hooks | src/hooks.server.ts |
| Client hooks | src/hooks.client.ts |
| Types | src/lib/types/ or co-located |
| Reactive module | src/lib/state/name.svelte.ts — runes require .svelte.ts extension outside components |
Components in src/lib/ are importable via $lib/components/.... Standalone Svelte projects use src/lib/components/ or src/components/.
4. DATA FETCHING PATTERNS
| Context | Pattern |
|---|---|
| Server load (primary) | +page.server.ts: export const load = async ({ fetch }) => { ... } |
| Page receives data | let { data } = $props() in +page.svelte |
| Client-side query | createQuery({ queryKey, queryFn }) via @tanstack/svelte-query |
| API route | +server.ts with export async function GET({ url }) { ... } |
| Form mutation | +page.server.ts with export const actions = { default: async ({ request }) => { ... } } |
Default: use +page.server.ts load functions. Never use onMount + fetch for initial data.
5. CLIENT / SERVER BOUNDARIES
- No
"use client"directive. Svelte components run on both server and client by default. - Server-only code:
+page.server.ts,+server.ts, or any*.server.tsfile. - Form actions in
+page.server.tsprovide progressive enhancement (works without JS). - Environment variables:
$env/static/privatefor server secrets,$env/static/publicfor client-safe values. - Dynamic env:
$env/dynamic/privateand$env/dynamic/publicwhen values change at runtime.
6. STYLING SYNTAX
| Method | Syntax |
|---|---|
| Tailwind | class="flex items-center gap-2 text-sm" |
| Class directive | class:active={isActive} (toggle shorthand) |
| Scoped CSS | <style> block (auto-scoped, no keyword needed) |
7. FRAMEWORK-SPECIFIC LIBRARY CATEGORIES
| Category | Svelte Libraries |
|---|---|
| data_fetching | @tanstack/svelte-query, SvelteKit load functions (built-in) |
| forms | superforms, felte, @tanstack/svelte-form |
| state_management | $state (built-in Svelte 5), svelte/store (Svelte 4 compat) |
| icons | lucide-svelte, @steeze-ui/icons, unplugin-icons |
| animation | svelte/transition (built-in), svelte/animate (built-in), gsap |
| component_library | shadcn-svelte, skeleton, bits-ui, melt-ui, flowbite-svelte |
| tables | @tanstack/svelte-table, svelte-headless-table |
| charts | layerchart, pancake, chart.js via svelte-chartjs |
| toast | svelte-sonner, svelte-french-toast |
| dnd | svelte-dnd-action, @dnd-kit/svelte |
Rule: check package.json before importing. If a library from this table is already installed, use it. Never add a second library for the same category.
Library Boundary Values (SVG Chart Libraries)
Some libraries accept color/style values as string props, not CSS classes or variables. SVG-based chart libraries (layercake, d3, pancake) require hex/rgb strings because the SVG renderer does not resolve CSS custom properties at the attribute level.
Pattern: Resolve CSS variables at runtime with $state + $effect.
<script>
let chartColors = $state({ primary: '#000000' })
function resolveTokenColor(tokenVar) {
return getComputedStyle(document.documentElement)
.getPropertyValue(tokenVar)
.trim() || '#000000'
}
$effect(() => {
chartColors = {
primary: resolveTokenColor('--colors-primary'),
}
})
</script>
When runtime resolution is not feasible, hardcoding is acceptable but MUST include a comment linking the value to its token:
<!-- Token: colors.primary (#2563EB) — hardcoded for SVG chart compatibility -->
<rect fill="#2563EB" />
These values are exempt from the Phase 5 hardcoded-values audit (bucket A) because the library API requires them.