All skills
sveltejs avatar

/svelte-core-bestpractices

@1eb06fd official
by Sveltesveltejs/mcp331 stars
40

Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.

Use this Skill: https://skilld.dev/gh/sveltejs/mcp/svelte-core-bestpractices

This session only. Nothing lands on disk.

referencessnippet.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

<!--- copy: false  --->
{#snippet name()}...{/snippet}
<!--- copy: false  --->
{#snippet name(param1, param2, paramN)}...{/snippet}

Snippets, and render tags, are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...

{#each images as image}
	{#if image.href}
		<a href={image.href}>
			<figure>
				<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
				<figcaption>{image.caption}</figcaption>
			</figure>
		</a>
	{:else}
		<figure>
			<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
			<figcaption>{image.caption}</figcaption>
		</figure>
	{/if}
{/each}

...you can write this:

{#snippet figure(image)}
	<figure>
		<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
		<figcaption>{image.caption}</figcaption>
	</figure>
{/snippet}

{#each images as image}
	{#if image.href}
		<a href={image.href}>
			{@render figure(image)}
		</a>
	{:else}
		{@render figure(image)}
	{/if}
{/each}

Like function declarations, snippets can have an arbitrary number of parameters, which can have default values, and you can destructure each parameter. You cannot use rest parameters, however.

Snippet scope

Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the <script> tag or in {#each ...} blocks...

<!-- codeblock:start {"title":"Snippets"} -->
<!--- file: App.svelte --->
<script>
	let { message = `it's great to see you!` } = $props();
</script>

{#snippet hello(name)}
	<p>hello {name}! {message}!</p>
{/snippet}

{@render hello('alice')}
{@render hello('bob')}
<!-- codeblock:end -->

...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):

<div>
	{#snippet x()}
		{#snippet y()}...{/snippet}

		<!-- this is fine -->
		{@render y()}
	{/snippet}

	<!-- this will error, as `y` is not in scope -->
	{@render y()}
</div>

<!-- this will also error, as `x` is not in scope -->
{@render x()}

Snippets can reference themselves and each other:

<!-- codeblock:start {"title":"Self-referencing snippets"} -->
<!--- file: App.svelte --->
{#snippet blastoff()}
	<span>🚀</span>
{/snippet}

{#snippet countdown(n)}
	{#if n > 0}
		<span>{n}...</span>
		{@render countdown(n - 1)}
	{:else}
		{@render blastoff()}
	{/if}
{/snippet}

{@render countdown(10)}
<!-- codeblock:end -->

Passing snippets to components

Explicit props

Within the template, snippets are values just like any other. As such, they can be passed to components as props:

<!-- codeblock:start {"title":"Explicit snippet props"} -->
<!--- file: App.svelte --->
<script>
	import Table from './Table.svelte';

	const fruits = [
		{ name: 'apples', qty: 5, price: 2 },
		{ name: 'bananas', qty: 10, price: 1 },
		{ name: 'cherries', qty: 20, price: 0.5 }
	];
</script>

{#snippet header()}
	<th>fruit</th>
	<th>qty</th>
	<th>price</th>
	<th>total</th>
{/snippet}

{#snippet row(d)}
	<td>{d.name}</td>
	<td>{d.qty}</td>
	<td>{d.price}</td>
	<td>{d.qty * d.price}</td>
{/snippet}

<Table data={fruits} +++{header} {row}+++ />
<!--- file: Table.svelte --->
<script>
	let { data, header, row } = $props();
</script>

<table>
	{#if header}
		<thead>
			<tr>{@render header()}</tr>
		</thead>
	{/if}

	<tbody>
		{#each data as d}
			<tr>{@render row(d)}</tr>
		{/each}
	</tbody>
</table>

<style>
	table {
		text-align: left;
		border-spacing: 0;
	}

	tbody tr:nth-child(2n+1) {
		background: ButtonFace;
	}

	table :global(th), table :global(td) {
		padding: 0.5em;
	}
</style>
<!-- codeblock:end -->

Think about it like passing content instead of data to a component. The concept is similar to slots in web components.

Implicit props

As an authoring convenience, snippets declared directly inside a component implicitly become props on the component:

<!-- codeblock:start {"title":"Implicit snippet props"} -->
<!--- file: App.svelte --->
<script>
	import Table from './Table.svelte';

	const fruits = [
		{ name: 'apples', qty: 5, price: 2 },
		{ name: 'bananas', qty: 10, price: 1 },
		{ name: 'cherries', qty: 20, price: 0.5 }
	];
</script>

<Table data={fruits}>
	{#snippet header()}
		<th>fruit</th>
		<th>qty</th>
		<th>price</th>
		<th>total</th>
	{/snippet}

	{#snippet row(d)}
		<td>{d.name}</td>
		<td>{d.qty}</td>
		<td>{d.price}</td>
		<td>{d.qty * d.price}</td>
	{/snippet}
</Table>
<!--- file: Table.svelte --->
<script>
	let { data, header, row } = $props();
</script>

<table>
	{#if header}
		<thead>
			<tr>{@render header()}</tr>
		</thead>
	{/if}

	<tbody>
		{#each data as d}
			<tr>{@render row(d)}</tr>
		{/each}
	</tbody>
</table>

<style>
	table {
		text-align: left;
		border-spacing: 0;
	}

	tbody tr:nth-child(2n+1) {
		background: ButtonFace;
	}

	table :global(th), table :global(td) {
		padding: 0.5em;
	}
</style>
<!-- codeblock:end -->

Implicit children snippet

Any content inside the component tags that is not a snippet declaration implicitly becomes part of the children snippet:

<!-- codeblock:start {"title":"Implicit children snippet","selected":"Button.svelte"} -->
<!--- file: App.svelte --->
<script>
	import Button from './Button.svelte';
</script>

<Button>click me</Button>
<!--- file: Button.svelte --->
<script>
	let { children } = $props();
</script>

<!-- result will be <button>click me</button> -->
<button>{@render children()}</button>
<!-- codeblock:end -->

[!NOTE] Note that you cannot have a prop called children if you also have content inside the component — for this reason, you should avoid having props with that name

Optional snippet props

You can declare snippet props as being optional. You can either use optional chaining to not render anything if the snippet isn't set...

<script>
    let { children } = $props();
</script>

{@render children?.()}

...or use an #if block to render fallback content:

<script>
    let { children } = $props();
</script>

{#if children}
    {@render children()}
{:else}
    fallback content
{/if}

Typing snippets

Snippets implement the Snippet interface imported from 'svelte':

<script lang="ts">
	import type { Snippet } from 'svelte';

	interface Props {
		data: any[];
		children: Snippet;
		row: Snippet<[any]>;
	}

	let { data, children, row }: Props = $props();
</script>

With this change, red squigglies will appear if you try and use the component without providing a data prop and a row snippet. Notice that the type argument provided to Snippet is a tuple, since snippets can have multiple parameters.

We can tighten things up further by declaring a generic, so that data and row refer to the same type:

<script lang="ts" generics="T">
	import type { Snippet } from 'svelte';

	let {
		data,
		children,
		row
	}: {
		data: T[];
		children: Snippet;
		row: Snippet<[T]>;
	} = $props();
</script>

Exporting snippets

Snippets declared at the top level of a .svelte file can be exported from a <script module> for use in other components, provided they don't reference any declarations in a non-module <script> (whether directly or indirectly, via other snippets):

<!-- codeblock:start {"title":"Exported snippets","selected":"snippets.svelte"} -->
<!--- file: App.svelte --->
<script>
	import { add } from './snippets.svelte';
</script>

{@render add(1, 2)}
<!--- file: snippets.svelte --->
<script module>
	export { add };
</script>

{#snippet add(a, b)}
	{a} + {b} = {a + b}
{/snippet}
<!-- codeblock:end -->

[!NOTE] This requires Svelte 5.5.0 or newer

Programmatic snippets

Snippets can be created programmatically with the createRawSnippet API. This is intended for advanced use cases.

Snippets and slots

In Svelte 4, content can be passed to components using slots. Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides legitimate guidance and best practices for Svelte 5 development. It contains only documentation and code examples with no identified security risks.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    10 files scanned · No issues

  • ZeroLeaks5mo

    1 finding · Score: 86/100

Signed by skilld at 1eb06fd. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated 2 months ago
  • svelte
  • reactivity
  • runes
  • components
  • styling
  • state-management
  • event-handling
  • best-practices
  • svelte5

README badge

README badge for sveltejs/mcp/svelte-core-bestpractices

Provides best practices for writing Svelte 5 components using runes (`$state`, `$derived`, `$effect`, `$props`), event handling, snippets, styling, and context. Load this skill in Svelte projects when writing or reviewing components to ensure idiomatic reactivity patterns and avoid legacy features.

Generated from the current SKILL.md.

Should I use $state for all variables?
No. Only use $state for variables that should be reactive and trigger updates in effects, derived values, or templates. Regular variables can stay as normal JavaScript. For large objects that are only reassigned (not mutated), use $state.raw instead to avoid proxy overhead.
When should I use $derived instead of $effect?
$derived should be your first choice for computing values from state. Use $effect only as an escape hatch for side effects like syncing to external libraries. Never use $effect just to update a variable based on state changes.
Can I use $effect to listen to window or document events?
No. Use <svelte:window> and <svelte:document> instead. Avoid $effect or onMount for attaching global event listeners.
What should I use instead of on:click and other legacy event directives?
Use the onclick attribute directly (e.g. <button onclick={() => {...}}>), which works with attribute shorthand and spread props.
Should I destructure items in each blocks if I need to bind to them?
No. Avoid destructuring if you need to mutate the item with something like bind:value={item.count}, as it can break reactivity.

Generated from the current SKILL.md. These answers refresh after source changes.