git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/svelte-component-composition" ~/.claude/skills/intense-visions-harness-engineering-svelte-component-composition-478835 && rm -rf "$T"
agents/skills/codex/svelte-component-composition/SKILL.mdSvelte Component Composition
Build flexible components in Svelte 5 using snippets, {@render}, typed children props, and named content areas
When to Use
- You are building a layout component (Card, Modal, Dialog) that renders caller-provided content
- You need multiple named content areas in a single component (header, body, footer slots)
- You are migrating Svelte 4
patterns to Svelte 5 snippets<slot> - You want to pass render functions as props for renderless/headless component patterns
Instructions
Svelte 5 snippets — defining reusable template blocks:
- Define snippets with
and render them with{#snippet name(params)}
:{@render name()}
{#snippet greeting(name: string)} <p>Hello, <strong>{name}</strong>!</p> {/snippet} {@render greeting('Alice')} {@render greeting('Bob')}
Children prop — default content slot:
- Accept default content from a parent using the
prop (typed aschildren
):Snippet
<!-- Card.svelte --> <script lang="ts"> import type { Snippet } from 'svelte' let { children }: { children: Snippet } = $props() </script> <div class="card"> {@render children()} </div>
<!-- Usage --> <Card> <p>This content is passed as children.</p> </Card>
- Make children optional with a fallback:
<script lang="ts"> import type { Snippet } from 'svelte' let { children }: { children?: Snippet } = $props() </script> {#if children} {@render children()} {:else} <p>No content provided.</p> {/if}
Named snippets — multiple content areas:
- Accept named snippet props for multiple content areas:
<!-- Dialog.svelte --> <script lang="ts"> import type { Snippet } from 'svelte' let { title, children, footer }: { title: Snippet children: Snippet footer?: Snippet } = $props() </script> <dialog> <header>{@render title()}</header> <main>{@render children()}</main> {#if footer} <footer>{@render footer()}</footer> {/if} </dialog>
<!-- Usage --> <Dialog> {#snippet title()} <h2>Confirm Delete</h2> {/snippet} <p>Are you sure you want to delete this item?</p> {#snippet footer()} <button onclick={cancel}>Cancel</button> <button onclick={confirm}>Delete</button> {/snippet} </Dialog>
Snippets with parameters — render prop pattern:
- Pass parameters through snippets to implement render prop / headless patterns:
<!-- List.svelte --> <script lang="ts"> import type { Snippet } from 'svelte' let { items, renderItem }: { items: Item[] renderItem: Snippet<[Item, number]> } = $props() </script> <ul> {#each items as item, index} <li>{@render renderItem(item, index)}</li> {/each} </ul>
<!-- Usage --> <List {items}> {#snippet renderItem(item, index)} <span>{index + 1}. {item.name}</span> {/snippet} </List>
Svelte 4 migration — slots to snippets:
- Replace
with<slot>
and named slots with named snippet props:{@render children()}
| Svelte 4 | Svelte 5 |
|---|---|
| |
| |
| |
| |
Passing snippets programmatically:
- Use
fromrenderSnippet
when you need to render a snippet in a non-template context (e.g., passing to a library):svelte
import { renderSnippet } from 'svelte'; const rendered = renderSnippet(mySnippet, args);
Details
Snippets vs. components:
Snippets are template fragments — they share the reactive scope of their defining component. Components are isolated scopes with their own lifecycle. Use snippets when you need to inject templating into a parent component; use child components when you need encapsulation.
Snippet type signatures:
import type { Snippet } from 'svelte'; Snippet; // no params Snippet<[]>; // explicit no params Snippet<[string]>; // one string param Snippet<[Item, number]>; // two params
{@render} with null safety:
{@render} will throw if passed undefined. Always check optionality:
{#if footer} {@render footer()} {/if}
Or use the
?? pattern with a fallback snippet.
Svelte 4 backward compatibility:
<slot> still works in Svelte 5 but is deprecated. The children snippet prop is backward-compatible — components using {@render children()} can receive content from both Svelte 5 snippet syntax and legacy <slot> fallback in child components.
Composing deeply nested layouts:
For deeply nested layouts (e.g., shell > sidebar > content), pass snippet props down or use the context API to avoid prop threading. The context API allows any descendant to read values without passing through intermediate components.
Source
https://svelte.dev/docs/svelte/snippet
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.