skills/role-linguist-svelte/SKILL.md
Svelte 5 and SvelteKit: runes reactivity, component composition, routing, data loading, form handling.
npx skillsauth add jasonwarrenuk/goblin-mode svelte-ninjaInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive guide to Svelte 5 and SvelteKit development patterns. Emphasizes runes-based reactivity ($state, $derived, $effect, $props), component composition, SvelteKit routing, data loading, form handling, and performance optimization.
Use this skill when:
Runes are compiler instructions (marked with $) that enable explicit reactivity:
$state - Reactive state$derived - Computed values$effect - Side effects$props - Component props$bindable - Two-way bindable propsKey principle: Reactivity is explicit, not implicit. Works in .js, .ts, and .svelte files.
<script>
let count = $state(0);
function increment() {
count++; // Just a number, no wrapper needed
}
</script>
<button onclick={increment}>
Clicks: {count}
</button>
Key points:
$state creates reactive state.value or getCount())<script>
let todos = $state([
{ id: 1, text: 'Learn Svelte 5', done: false }
]);
function toggle(id) {
const todo = todos.find(t => t.id === id);
todo.done = !todo.done; // Deep reactivity works
}
function addTodo(text) {
todos.push({ id: Date.now(), text, done: false });
// Array methods trigger reactivity
}
</script>
Deep reactivity:
.push, .splice, etc.) work reactively// counter.svelte.js
export function createCounter(initial = 0) {
let count = $state(initial);
return {
get count() { return count; },
increment: () => count++,
reset: () => count = initial
};
}
<!-- App.svelte -->
<script>
import { createCounter } from './counter.svelte.js';
const counter = createCounter(5);
</script>
<button onclick={counter.increment}>
Count: {counter.count}
</button>
Benefits:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let isEven = $derived(count % 2 === 0);
</script>
<p>{count} doubled is {doubled}</p>
<p>Count is {isEven ? 'even' : 'odd'}</p>
Key points:
<script>
let numbers = $state([1, 2, 3, 4, 5]);
let stats = $derived.by(() => {
const total = numbers.reduce((a, b) => a + b, 0);
const average = total / numbers.length;
return { total, average };
});
</script>
<p>Total: {stats.total}, Average: {stats.average}</p>
Use $derived.by when:
$derived - Computing values (pure, returns value):
<script>
let count = $state(0);
let doubled = $derived(count * 2); // ✓ Good
</script>
$effect - Side effects (impure, no return value):
<script>
let count = $state(0);
$effect(() => {
console.log('Count changed:', count); // ✓ Good
});
</script>
<script>
let count = $state(0);
$effect(() => {
// Runs on mount and whenever count changes
document.title = `Count: ${count}`;
});
</script>
When $effect runs:
$:)<script>
let intervalId = $state(null);
let elapsed = $state(0);
$effect(() => {
const id = setInterval(() => {
elapsed++;
}, 1000);
// Cleanup runs when effect re-runs or component unmounts
return () => clearInterval(id);
});
</script>
<script>
let messages = $state([]);
let div;
$effect.pre(() => {
const isAtBottom =
div.scrollHeight - div.scrollTop === div.clientHeight;
if (isAtBottom) {
// After DOM updates, scroll to bottom
$effect(() => {
div.scrollTop = div.scrollHeight;
});
}
});
</script>
Local storage sync:
<script>
let preferences = $state(JSON.parse(
localStorage.getItem('prefs') || '{}'
));
$effect(() => {
localStorage.setItem('prefs', JSON.stringify(preferences));
});
</script>
API calls:
<script>
let userId = $state('123');
let user = $state(null);
$effect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => user = data);
});
</script>
Avoid $effect for:
$derived)<svelte:head>)<!-- Button.svelte -->
<script>
let { label, variant = 'primary' } = $props();
</script>
<button class="btn btn--{variant}">
{label}
</button>
Usage:
<Button label="Click me" variant="secondary" />
<script lang="ts">
interface Props {
label: string;
variant?: 'primary' | 'secondary' | 'danger';
onclick?: () => void;
}
let { label, variant = 'primary', onclick }: Props = $props();
</script>
<button class="btn btn--{variant}" {onclick}>
{label}
</button>
<script>
let { label, ...rest } = $props();
</script>
<button {...rest}>
{label}
</button>
<!-- Input.svelte -->
<script>
let { value = $bindable('') } = $props();
</script>
<input bind:value />
Usage:
<script>
let text = $state('');
</script>
<Input bind:value={text} />
<p>You typed: {text}</p>
Deep-dive detail lives in supporting files, loaded only when needed:
<!-- ✗ Bad -->
<script>
let count = $state(0);
let doubled = $state(0);
$effect(() => {
doubled = count * 2; // Wrong! Use $derived
});
</script>
<!-- ✓ Good -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<!-- ✗ Bad -->
<script>
let { user } = $props();
function updateName() {
user.name = 'New Name'; // Wrong! Props are read-only
}
</script>
<!-- ✓ Good -->
<script>
let { user, onUpdate } = $props();
function updateName() {
onUpdate({ ...user, name: 'New Name' });
}
</script>
<!-- ✗ Bad -->
<script>
let count = $state(0);
let doubled = $state(0);
let isEven = $state(false);
function increment() {
count++;
doubled = count * 2; // Derived!
isEven = count % 2 === 0; // Derived!
}
</script>
<!-- ✓ Good -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let isEven = $derived(count % 2 === 0);
</script>
Svelte/SvelteKit code is well-structured when:
tools
{{ 𝚫𝚫𝚫 }} Rebuild roadmap-system.zip, the distributable snapshot of the roadmap tooling (scripts, HTML template, conventions reference, and every roadmap-touching skill, including this one).
tools
--- name: "Suggest: Task" description: "{{ 𝚫𝚫𝚫 }} Suggest the next logical task — grounded in the roadmap's pre-vetted ready-set when one exists, codebase analysis otherwise" when_to_use: "When you don't know what to work on next and want a grounded recommendation rather than picking arbitrarily." model: haiku effort: low disable-model-invocation: true allowed-tools: ["Read", "Glob", "Grep", "Bash(python3:*)", "Bash(npm:*)", "Bash(bun:*)", "Bash(pnpm:*)", "Bash(deno:*)"] argument-hint: [named
development
{{ 𝛀𝛀𝛀 }} Convert an old simple-style roadmap (single Markdown, four statuses, <a name> anchors, roadmaps.json pointer registry) into the rich phase-array format (roadmaps.json source of truth + PHASE task list + prose overview).
data-ai
{{ ƔƔƔ }} Create a pull request to main — wordy or shiny (with screenshots), ready-for-review or draft