Web framework svelte
The official skills marketplace for Agents Inc, an agent composition framework that builds stacks and compiles specialized subagents for Claude Code
npx -y skills add agents-inc/skills --skill web-framework-svelteAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Svelte 5 Runes reactivity - $state, $derived, $effect, $props, $bindable, components, snippets, event handling, context API
SKILL.md
17.5 KB, as published. Nobody here has run it
Svelte 5 Patterns
Quick Guide: Svelte 5 uses Runes for explicit reactivity. Use
$statefor reactive variables,$derivedfor computed values,$effectonly as an escape hatch. Use snippets instead of slots. Use callback props instead of event dispatchers. Keep components small and composable.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use Svelte 5 Runes syntax — NOT Svelte 4 patterns like export let, $:, or stores for component state)
(You MUST use $derived for computed values — NEVER use $effect to synchronize state)
(You MUST use snippets ({#snippet} / {@render}) instead of slots (<slot>))
(You MUST use callback props (onclick, onsomething) instead of createEventDispatcher)
(You MUST use $state.raw() for large objects/arrays that are replaced, not mutated)
(You MUST use createContext for type-safe context instead of raw setContext/getContext with string keys)
</critical_requirements>
Auto-detection: Svelte 5, Runes, $state, $derived, $effect, $props, $bindable, $inspect, .svelte, snippet, @render, createContext, getContext, setContext, $state.raw, $state.eager, $derived.by, $effect.pre, ClassValue
When to use:
- Building Svelte 5 components with Runes reactivity
- Managing component state with
$stateand computed values with$derived - Creating reusable markup with snippets (replacing slots)
- Handling events with native event attributes and callback props
- Sharing state across components with context API
- Two-way binding with
$bindableprops
Key patterns covered:
- Runes:
$state,$derived,$effect,$props,$bindable,$inspect - Component composition with snippets and
{@render} - Event handling with native attributes and callback props
- Context API with
createContextfor type-safe cross-component state - Class-based reactive state with
$statefields - Deep vs shallow reactivity (
$statevs$state.raw)
When NOT to use:
- Meta-framework-specific patterns (routing, load functions, form actions) — use the corresponding meta-framework skill
- Svelte 4 patterns (
export let,$:reactive statements,<slot>,createEventDispatcher) - Server-side logic (use your meta-framework's server hooks and routes)
Detailed Resources:
- For decision frameworks and anti-patterns, see reference.md
Runes & Reactivity:
- examples/core.md -
$state,$derived,$effect,$props,$bindable, component patterns
Component Patterns:
- examples/snippets.md - Snippet blocks,
{@render}, passing snippets as props, replacing slots - examples/events.md - Event handling, component events via callback props, event modifiers
Advanced:
- examples/advanced.md -
$inspect, context API,$state.raw,$state.eager, class-based state, shared state modules
<philosophy>
Philosophy
Svelte 5 introduces Runes — a set of primitives that bring explicit, fine-grained reactivity to Svelte. Unlike Svelte 4's compiler magic ($:, export let), Runes make reactivity visible and portable across .svelte files, .ts files, and class definitions.
Core principles:
- Explicit reactivity — Runes (
$state,$derived,$effect) make reactive declarations visible. No hidden compiler transformations. - Derived over effects — Compute values with
$derived, not$effect. Effects are escape hatches, not primary tools. - Deep reactivity by default —
$statecreates deeply reactive proxies for objects/arrays. Mutations are tracked automatically. - Snippets replace slots —
{#snippet}blocks are more powerful, typed, and composable than<slot>elements. - Callback props replace event dispatchers — Pass
onsomethingcallback props instead of usingcreateEventDispatcher. - Compile-time optimization — Svelte compiles components to efficient imperative code. No virtual DOM diffing at runtime.
When to use Svelte 5 Runes:
- All new Svelte components (Runes are the default in Svelte 5)
- Reactive state in
.svelte.tsor.svelte.jsfiles - Class-based state with reactive fields
- Any computed value that depends on reactive state
When NOT to use:
- Non-reactive constants (use plain
constorlet) - Server-side code that doesn't need reactivity
- Meta-framework concerns (routing, load functions, server hooks) — use the corresponding meta-framework skill
- Svelte 4 patterns —
export let,$:, stores for component state,<slot>,createEventDispatcher
<patterns>
Core Patterns
Pattern 1: Reactive State with $state
Use $state to declare reactive variables. Updates to $state variables automatically trigger UI re-renders.
<!-- counter.svelte -->
<script lang="ts">
let count = $state(0);
const STEP = 5;
function increment() {
count += 1;
}
function incrementByStep() {
count += STEP;
}
</script>
<button onclick={increment}>
Count: {count}
</button>
<button onclick={incrementByStep}>
+{STEP}
</button>
Why good: Explicit reactive declaration, named constants for magic numbers, plain function event handlers
<!-- BAD: Svelte 4 style -->
<script>
let count = 0; // Not explicitly reactive in Svelte 5 mode
$: doubled = count * 2; // Svelte 4 reactive statement
</script>
Why bad: $: is Svelte 4 syntax deprecated in Svelte 5, implicit reactivity is confusing and non-portable
Deep Reactivity
$state creates deep proxies for objects and arrays — mutations are tracked automatically:
<script lang="ts">
interface Todo {
done: boolean;
text: string;
}
let todos = $state<Todo[]>([
{ done: false, text: 'Learn Svelte 5' }
]);
function addTodo(text: string) {
todos.push({ done: false, text }); // Mutation tracked!
}
function toggleTodo(index: number) {
todos[index].done = !todos[index].done; // Deep mutation tracked!
}
</script>
Why good: No need for immutable update patterns, array methods like .push() trigger reactivity, property mutations tracked deeply
Pattern 2: Computed Values with $derived
Use $derived for values that depend on other reactive state. Never use $effect to synchronize state.
<script lang="ts">
let count = $state(0);
// Simple expression
let doubled = $derived(count * 2);
// Complex computation with $derived.by
let stats = $derived.by(() => {
const isEven = count % 2 === 0;
const isPositive = count > 0;
return { isEven, isPositive };
});
</script>
<p>{count} doubled is {doubled}</p>
<p>Even: {stats.isEven}, Positive: {stats.isPositive}</p>
Why good: Automatically recalculates when dependencies change, no side effects, push-pull reactivity avoids unnecessary recalculations
<!-- BAD: Using $effect to synchronize state -->
<script lang="ts">
let count = $state(0);
let doubled = $state(0);
$effect(() => {
doubled = count * 2; // WRONG: Use $derived instead
});
</script>
Why bad: $effect for derived state creates unnecessary reactive subscriptions, runs after DOM update (timing issues), harder to reason about data flow
Pattern 3: Component Props with $props
Use $props to declare component inputs. Supports destructuring, defaults, rest props, and TypeScript.
<!-- user-card.svelte -->
<script lang="ts">
interface Props {
name: string;
email: string;
role?: string;
class?: string;
}
let { name, email, role = 'member', ...rest }: Props = $props();
// Derived from props — updates when props change
let initials = $derived(
name.split(' ').map(n => n[0]).join('').toUpperCase()
);
</script>
<div class="user-card" {...rest}>
<span class="avatar">{initials}</span>
<h3>{name}</h3>
<p>{email}</p>
<span class="badge">{role}</span>
</div>
Why good: Type-safe props with interface, destructuring with defaults, rest props for pass-through, derived values update with prop changes
<!-- BAD: Svelte 4 style -->
<script>
export let name; // Svelte 4 prop declaration
export let email;
export let role = 'member';
</script>
Why bad: export let is Svelte 4 syntax deprecated in Svelte 5, no type safety, no rest props
Pattern 4: Two-Way Binding with $bindable
Use $bindable to declare props that support two-way binding with bind:. Use sparingly — prefer one-way data flow.
<!-- text-input.svelte -->
<script lang="ts">
interface Props {
value: string;
placeholder?: string;
}
let { value = $bindable(''), placeholder = '' }: Props = $props();
</script>
<input
bind:value={value}
{placeholder}
class="text-input"
/>
<!-- parent.svelte -->
<script lang="ts">
import TextInput from './text-input.svelte';
let searchQuery = $state('');
</script>
<TextInput bind:value={searchQuery} placeholder="Search..." />
<p>Searching for: {searchQuery}</p>
Why good: Explicit two-way binding declaration, parent controls the state, child can modify via bind:, TypeScript-safe
When to use: Form inputs, UI primitives (sliders, toggles) where two-way binding simplifies the API
When not to use: Most component communication — prefer callback props for explicit data flow
Pattern 5: Side Effects with $effect
Use $effect for side effects that need to run when reactive state changes. This is an escape hatch — prefer $derived for computed values and event handlers for user-triggered actions.
<script lang="ts">
let searchQuery = $state('');
let results = $state<string[]>([]);
const DEBOUNCE_MS = 300;
// Good: Side effect for external API calls
$effect(() => {
const query = searchQuery;
if (!query) {
results = [];
return;
}
const timer = setTimeout(async () => {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
results = await response.json();
}, DEBOUNCE_MS);
// Cleanup function runs before next effect and on unmount
return () => clearTimeout(timer);
});
</script>
<input bind:value={searchQuery} placeholder="Search..." />
{#each results as result}
<p>{result}</p>
{/each}
Why good: External API call is a legitimate side effect, cleanup prevents stale requests, named constant for debounce
When NOT to Use $effect
<script lang="ts">
let count = $state(0);
// BAD: Synchronizing state — use $derived
// $effect(() => { doubled = count * 2; });
// BAD: Logging in effect — use $inspect for debugging
// $effect(() => { console.log(count); });
// BAD: Calling functions on change — use event handlers
// $effect(() => { if (count > 10) showAlert(); });
// GOOD: Use $derived for computed values
let doubled = $derived(count * 2);
</script>
Pattern 6: Snippets (Replacing Slots)
Snippets are reusable markup blocks declared with {#snippet} and rendered with {@render}. They replace Svelte 4's <slot> elements.
<!-- card.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
title: string;
children: Snippet;
footer?: Snippet;
}
let { title, children, footer }: Props = $props();
</script>
<div class="card">
<h2>{title}</h2>
<div class="card-body">
{@render children()}
</div>
{#if footer}
<div class="card-footer">
{@render footer()}
</div>
{/if}
</div>
<!-- usage -->
<script lang="ts">
import Card from './card.svelte';
</script>
<Card title="Welcome">
<p>This becomes the children snippet automatically.</p>
{#snippet footer()}
<button>Learn More</button>
{/snippet}
</Card>
Why good: Type-safe with Snippet type, optional snippets with conditional rendering, children is implicit for content between tags
<!-- BAD: Svelte 4 slots -->
<div class="card">
<slot /> <!-- Deprecated in Svelte 5 -->
<slot name="footer" /> <!-- Use snippets instead -->
</div>
Why bad: <slot> is deprecated in Svelte 5, no type safety, less composable than snippets
Pattern 7: Event Handling
Svelte 5 uses native event attributes (onclick, onsubmit) instead of Svelte 4's on:click directive. Component events use callback props.
Element Events
<script lang="ts">
let count = $state(0);
function handleClick(event: MouseEvent) {
count += 1;
}
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
// handle form
}
</script>
<button onclick={handleClick}>Clicked {count} times</button>
<!-- Inline handlers are fine for simple logic -->
<button onclick={() => count = 0}>Reset</button>
<form onsubmit={handleSubmit}>
<input name="query" />
<button type="submit">Search</button>
</form>
Component Events via Callback Props
<!-- color-picker.svelte -->
<script lang="ts">
interface Props {
color: string;
onchange?: (color: string) => void;
onreset?: () => void;
}
let { color, onchange, onreset }: Props = $props();
const COLORS = ['red', 'green', 'blue', 'purple'] as const;
</script>
{#each COLORS as c}
<button
onclick={() => onchange?.(c)}
class={{ selected: color === c }}
>
{c}
</button>
{/each}
{#if onreset}
<button onclick={onreset}>Reset</button>
{/if}
<!-- parent.svelte -->
<script lang="ts">
import ColorPicker from './color-picker.svelte';
let selectedColor = $state('red');
</script>
<ColorPicker
color={selectedColor}
onchange={(c) => selectedColor = c}
onreset={() => selectedColor = 'red'}
/>
Why good: Type-safe callback props, optional with ?. call, parent controls event handling, no indirection through dispatcher
<!-- BAD: Svelte 4 event dispatcher -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function handleClick() {
dispatch('change', { color: 'red' }); // Deprecated pattern
}
</script>
Why bad: createEventDispatcher is deprecated in Svelte 5, no type safety, requires manual event typing
<integration>
Integration Guide
Styling integration:
- Scoped
<style>blocks are the default — styles don't leak to other components - Use
:global()for global styles or CSS custom properties for parent-to-child styling - Any CSS approach (CSS Modules, utility-first, preprocessors) works with Svelte
State management:
$statefor component-local state- Context API (
createContext) for subtree-scoped state - Reactive classes with
$statefields for shared state modules (.svelte.ts) - Meta-framework load functions for server state
TypeScript integration:
- Full TypeScript support in
<script lang="ts">blocks Snippet<[ParamType]>for typed snippet props- Interface-based prop typing with
$props() ClassValuetype fromsvelte/elementsfor type-safe class props (Svelte 5.19+)
<red_flags>
RED FLAGS
High Priority:
- Using
export letfor props — use$props()instead - Using
$:reactive statements — use$derivedor$effect - Using
<slot>or<slot name="x">— use{#snippet}and{@render} - Using
createEventDispatcher— use callback props - Using
$effectto sync state — use$derivedfor computed values - Destructuring
$stateobjects — breaks reactivity (values captured at destructure time)
Medium Priority:
- Using
on:clickdirective — useonclickattribute - Not using
$state.raw()for large API responses — unnecessary proxy overhead - Using
setContext/getContextwith string keys — usecreateContextfor type safety - Using
class:name={condition}— use built-inclassattribute object/array syntax (since 5.16)
Gotchas:
$stateproxies are not the original object — use$state.snapshot()to get a plain copy- Destructuring
$statecaptures values, not references — access properties directly instead $derivedreturn values are NOT deeply reactive — only$statecreates deep proxies$effectruns after DOM update — use$effect.pre()for pre-update timing- Dependencies after
awaitin$effectare not tracked - Context must be set during component init — cannot call
setContextin event handlers or$effect
For complete decision frameworks and the full anti-patterns list, see reference.md.
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use Svelte 5 Runes syntax — NOT Svelte 4 patterns like export let, $:, or stores for component state)
(You MUST use $derived for computed values — NEVER use $effect to synchronize state)
(You MUST use snippets ({#snippet} / {@render}) instead of slots (<slot>))
(You MUST use callback props (onclick, onsomething) instead of createEventDispatcher)
(You MUST use $state.raw() for large objects/arrays that are replaced, not mutated)
(You MUST use createContext for type-safe context instead of raw setContext/getContext with string keys)
Failure to follow these rules will produce outdated Svelte 4 code that is deprecated and will break in future versions.
</critical_reminders>