Compound Component Pattern

+15 Mana ✨

Compound components are groups of components designed to work together. Snippets enable this pattern elegantly.

Traditional Approach (Multiple Components)

svelte
<!-- Old way: separate components for each part -->
<Tabs>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>Content 1</TabPanel>
    <TabPanel>Content 2</TabPanel>
  </TabPanels>
</Tabs>

Snippet Approach (Single Component)

svelte
<!-- Tabs.svelte -->
<script>
  let { tabs } = $props();
  let activeIndex = $state(0);
</script>

<div class="tabs">
  <div class="tab-list" role="tablist">
    {#each tabs as tab, index}
      <button
        class="tab"
        class:active={index === activeIndex}
        onclick={() => activeIndex = index}
        role="tab"
        aria-selected={index === activeIndex}
      >
        {@render tab.label()}
      </button>
    {/each}
  </div>
  
  <div class="tab-panel" role="tabpanel">
    {@render tabs[activeIndex].content()}
  </div>
</div>
svelte
<!-- Usage -->
<Tabs tabs={[
  {
    label: () => <span>🏠 Home</span>,
    content: () => <div>Home content</div>
  },
  {
    label: () => <span>⚙️ Settings</span>,
    content: () => <SettingsPanel />
  }
]} />

Wait — that doesn't work because we can't define snippets inline like that. Let's fix it:

The Correct Pattern

svelte
<!-- Usage -->
<Tabs>
  {#snippet tabs()}
    {#snippet tab1Label()}🏠 Home{/snippet}
    {#snippet tab1Content()}<div>Home content</div>{/snippet}
    
    {#snippet tab2Label()}⚙️ Settings{/snippet}
    {#snippet tab2Content()}<SettingsPanel />{/snippet}
  {/snippet}
</Tabs>

Actually, let's use a more practical pattern:

Clean API with Named Snippets

svelte
<!-- Card.svelte -->
<script>
  let { 
    header,
    children,
    footer,
    image,
    actions
  } = $props();
</script>

<div class="card">
  {#if image}
    <div class="card-image">
      {@render image()}
    </div>
  {/if}
  
  {#if header}
    <header class="card-header">
      {@render header()}
    </header>
  {/if}
  
  <div class="card-body">
    {@render children?.()}
  </div>
  
  {#if actions}
    <div class="card-actions">
      {@render actions()}
    </div>
  {/if}
  
  {#if footer}
    <footer class="card-footer">
      {@render footer()}
    </footer>
  {/if}
</div>
svelte
<!-- Clean usage -->
<Card>
  {#snippet image()}
    <img src={product.image} alt={product.name} />
  {/snippet}
  
  {#snippet header()}
    <h3>{product.name}</h3>
    <span class="price">${product.price}</span>
  {/snippet}
  
  <p>{product.description}</p>
  
  {#snippet actions()}
    <button onclick={() => addToCart(product)}>Add to Cart</button>
    <button onclick={() => addToWishlist(product)}>♡</button>
  {/snippet}
</Card>
✓ Completed