developing-svelte
Use when working with .svelte files, SvelteKit projects, or Svelte reactivity. Covers component structure, stores, routing, Svelte 5 runes, and testing patterns.
When & Why to Use This Skill
This Claude skill provides comprehensive guidance and best practices for developing modern web applications using Svelte and SvelteKit. It covers essential patterns including component structure, state management with stores, file-based routing, and the latest Svelte 5 runes ($state, $derived, $effect) to ensure high-performance, reactive, and maintainable codebases.
Use Cases
- Building reactive UI components using Svelte 5 runes for optimized state management and performance.
- Implementing complex server-side rendering (SSR) and file-based routing within SvelteKit projects.
- Architecting global state management using Svelte stores and context APIs for efficient cross-component communication.
- Migrating legacy Svelte 4 codebases to Svelte 5 by applying new reactivity patterns and syntax.
- Writing robust unit and integration tests for Svelte components using Vitest and Svelte Testing Library.
- Developing progressive form enhancements using SvelteKit form actions and the use:enhance directive.
| name | developing-svelte |
|---|---|
| description | Use when working with .svelte files, SvelteKit projects, or Svelte reactivity. Covers component structure, stores, routing, Svelte 5 runes, and testing patterns. |
Svelte Development
Apply these patterns when working with Svelte components and SvelteKit applications.
Component Structure
<script lang="ts">
import { onMount } from 'svelte';
import Component from './Component.svelte';
export let propName: string;
export let optionalProp: number = 0;
let count = 0;
$: doubled = count * 2;
function increment() {
count += 1;
}
onMount(() => {
return () => { /* cleanup */ };
});
</script>
<div class="container">
<h1>{propName}</h1>
<button on:click={increment}>Count: {count}</button>
</div>
<style>
.container { padding: 1rem; }
</style>
Reactivity
<script>
let count = 0;
$: doubled = count * 2;
$: if (count > 10) alert('High count!');
</script>
Store Subscriptions
<script>
import { writable, derived } from 'svelte/store';
const count = writable(0);
const doubled = derived(count, $count => $count * 2);
function increment() {
$count += 1;
}
</script>
<p>Count: {$count}, Doubled: {$doubled}</p>
Stores
Writable Store
import { writable } from 'svelte/store';
function createCounter() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
reset: () => set(0)
};
}
export const counter = createCounter();
Derived Store
import { derived } from 'svelte/store';
import { items } from './items';
export const totalPrice = derived(items, $items =>
$items.reduce((sum, item) => sum + item.price, 0)
);
Context vs Stores
Use stores for state shared across unrelated components. Use context for state scoped to a component tree.
<script>
import { setContext, getContext } from 'svelte';
setContext('theme', { color: 'dark' });
const theme = getContext('theme');
</script>
SvelteKit
File-based Routing
src/routes/
├── +page.svelte # /
├── +layout.svelte
├── about/+page.svelte # /about
├── blog/[slug]/+page.svelte # /blog/:slug
└── api/users/+server.ts # /api/users
Page Load
// +page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, fetch }) => {
const res = await fetch(`/api/posts/${params.slug}`);
return { post: await res.json() };
};
<!-- +page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<h1>{data.post.title}</h1>
Form Actions
// +page.server.ts
import type { Actions } from './$types';
import { fail, redirect } from '@sveltejs/kit';
export const actions: Actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
if (!email) return fail(400, { email, missing: true });
throw redirect(303, '/success');
}
};
<script>
import { enhance } from '$app/forms';
</script>
<form method="POST" use:enhance>
<input name="email" type="email" />
<button>Submit</button>
</form>
Svelte 5 Runes
<script>
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
console.log('count changed:', count);
});
let { name, value = 0 }: { name: string; value?: number } = $props();
</script>
| Svelte 4 | Svelte 5 |
|---|---|
let count = 0 |
let count = $state(0) |
$: doubled = count * 2 |
let doubled = $derived(count * 2) |
$: { ... } |
$effect(() => { ... }) |
export let prop |
let { prop } = $props() |
Testing
import { render, screen, fireEvent } from '@testing-library/svelte';
import { describe, it, expect } from 'vitest';
import Counter from './Counter.svelte';
describe('Counter', () => {
it('increments count on click', async () => {
render(Counter);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('Count: 0');
await fireEvent.click(button);
expect(button).toHaveTextContent('Count: 1');
});
});
Requirements
- Keep components small; split large ones
- Use TypeScript with
lang="ts" - Prefer
$:overonMountfor derived state - Use
{#each items as item (item.id)}with keys - Use scoped component styles
- Use
use:enhancefor progressive form enhancement - Colocate load functions with pages