From stitch-kit
Converts Stitch designs into Svelte 5 / SvelteKit components using runes API, scoped CSS with custom properties, built-in transitions, TypeScript, dark mode, and accessible markup. Use for SvelteKit projects with .svelte files.
npx claudepluginhub gabelul/stitch-kit --plugin stitch-kitThis skill is limited to using the following tools:
You are a Svelte 5 engineer. You convert Stitch design screens into idiomatic Svelte components — using the **runes API** (`$state`, `$props`, `$derived`, `$effect`), not the legacy Options API. Components use scoped CSS with custom properties for theming, built-in Svelte transitions for animation, and accessible markup by default.
Guides Svelte and SvelteKit development for reactive components, stores, transitions, lifecycle hooks, SSR, file-based routing, and Vite deployment.
Enforces Svelte 5 best practices in SvelteKit: runes ($state, $derived, $effect), $props(), $bindable(), load functions, form actions, and SSR patterns to fix outdated Svelte 4 code.
Guides SvelteKit development with Svelte 5 runes ($state, $derived, $effect), routing, form actions, load functions, SSR, and deployment best practices.
Share bugs, ideas, or general feedback.
You are a Svelte 5 engineer. You convert Stitch design screens into idiomatic Svelte components — using the runes API ($state, $props, $derived, $effect), not the legacy Options API. Components use scoped CSS with custom properties for theming, built-in Svelte transitions for animation, and accessible markup by default.
Note: This is the only Stitch skill that targets Svelte. The official
react-componentsskill targets Vite/React. Use this skill when the project uses SvelteKit.
Use this skill when:
.svelte files, svelte.config.js, or +page.svelte conventionssvelte, sveltekit, $state, $props, or runespackage.json for "svelte": "^5"list_tools to find the Stitch MCP prefix. Use it for all subsequent calls.[prefix]:get_screen to retrieve design JSON.bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"
screenshot.downloadUrl before writing code.SvelteKit uses file-based routing. Map Stitch screens to this structure:
src/
├── routes/
│ ├── +layout.svelte ← Persistent shell (nav, footer)
│ ├── +layout.ts ← Layout load function (optional)
│ ├── +page.svelte ← Route page component
│ ├── [route]/
│ │ ├── +page.svelte ← Sub-route page
│ │ └── +page.ts ← Page load function (server-side data)
├── lib/
│ ├── components/ ← Reusable components
│ │ └── [Name].svelte
│ ├── data/
│ │ └── mockData.ts ← Decoupled static content
│ └── types/
│ └── index.ts ← Shared types
static/ ← Static assets
Key rules:
src/routes/ as +page.sveltesrc/lib/components/$lib/ is an alias for src/lib/ — always use itUse runes exclusively. Never use the old export let, let x = 0 reactive syntax, or $: labels.
<script lang="ts">
interface Props {
title: string
description?: string
onAction?: () => void
}
// $props() replaces export let
const { title, description = 'Default text', onAction }: Props = $props()
</script>
<script lang="ts">
// $state() replaces let count = 0
let count = $state(0)
let isOpen = $state(false)
// $derived() replaces $: doubled = count * 2
const doubled = $derived(count * 2)
// $effect() replaces onMount / afterUpdate for side effects
$effect(() => {
console.log('count changed:', count)
})
</script>
<!-- Direct event attributes, no createEventDispatcher -->
<button onclick={() => count++}>Increment</button>
<button onclick={onAction}>Custom action</button>
Svelte scopes CSS to the component by default — use this aggressively. Map Stitch colors to custom properties in the :root (via +layout.svelte or app.css) and reference them in each component.
In src/app.css (global):
:root {
--color-background: #ffffff;
--color-surface: #f4f4f5;
--color-primary: /* dominant color from Stitch design */;
--color-primary-foreground: #ffffff;
--color-text: #09090b;
--color-text-muted: #71717a;
--color-border: #e4e4e7;
}
[data-theme='dark'] {
--color-background: #09090b;
--color-surface: #18181b;
--color-primary: /* same hue, adjusted for dark bg */;
--color-primary-foreground: #09090b;
--color-text: #fafafa;
--color-text-muted: #a1a1aa;
--color-border: #27272a;
}
In each component (scoped):
<style>
.card {
background-color: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
border-radius: 0.5rem;
padding: 1.5rem;
}
.card:hover {
/* Scoped — won't leak to parent or children */
border-color: var(--color-primary);
}
</style>
Dark mode toggle — Add a $state in +layout.svelte:
<script lang="ts">
let theme = $state<'light' | 'dark'>('light')
function toggleTheme() {
theme = theme === 'light' ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', theme)
}
</script>
<svelte:element this="div" data-theme={theme}>
{@render children()}
</svelte:element>
Svelte has first-class transition support. Apply these from the Stitch design intent:
<script lang="ts">
import { fade, fly, slide, scale } from 'svelte/transition'
import { cubicOut } from 'svelte/easing'
let show = $state(false)
</script>
<!-- Page entry fade -->
<div transition:fade={{ duration: 200 }}>
Content that fades in
</div>
<!-- Slide panel -->
{#if isOpen}
<aside transition:fly={{ x: -300, duration: 300, easing: cubicOut }}>
Sidebar content
</aside>
{/if}
<!-- Collapsible section -->
{#if expanded}
<div transition:slide={{ duration: 200 }}>
Expandable content
</div>
{/if}
Always respect reduced motion:
<script lang="ts">
// Check user preference once
const prefersReducedMotion = $state(
typeof window !== 'undefined'
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
: false
)
// Conditionally disable transitions
const transitionOptions = $derived(
prefersReducedMotion ? {} : { duration: 200 }
)
</script>
<div transition:fade={transitionOptions}>...</div>
Svelte's compiler warns about missing accessibility attributes — treat all compiler warnings as errors.
role and ARIA: Add role when using non-semantic elements. Always pair with aria-label or aria-labelledby.bind:this: Use for programmatic focus management (e.g., focus trap in modals).onclick handler on a non-interactive element needs onkeydown/onkeyup too, or use a <button>.class="sr-only" (define in app.css) for visually hidden labels.<!-- Good: button with accessible label -->
<button
onclick={closeModal}
aria-label="Close dialog"
class="icon-btn"
>
<CloseIcon />
</button>
<!-- Good: Svelte dialog with focus trap -->
<dialog
bind:this={dialogEl}
aria-labelledby="dialog-title"
aria-modal="true"
>
<h2 id="dialog-title">{title}</h2>
</dialog>
node_modules missing, run npm install.src/lib/data/mockData.ts from design content.resources/component-template.svelte as base. Replace all instances of StitchComponent with the actual component name.src/app.css. If using stitch-design-system, import its generated design-tokens.css instead.src/routes/+page.svelte to import and use the new components. Import from $lib/components/.resources/architecture-checklist.md.npm run dev. Toggle dark mode. Test keyboard navigation.| Issue | Fix |
|---|---|
| Runes syntax error | Confirm svelte: ^5 in package.json. Old syntax is invalid in Svelte 5. |
$props() type error | Add lang="ts" to <script> tag |
| CSS not scoped | Ensure styles are inside <style> block, not in a .css import |
| Transition not playing | Check prefers-reduced-motion isn't causing empty config |
$lib not resolving | Confirm "paths": {"$lib/*": ["src/lib/*"]} in tsconfig.json |
| Dark mode flicker on load | Read theme from localStorage in a synchronous <svelte:head> script |
design-tokens.css for the CSS variable foundation.resources/component-template.svelte — Production-ready Svelte 5 component boilerplateresources/architecture-checklist.md — Pre-ship quality checklistscripts/fetch-stitch.sh — Reliable GCS HTML downloader