A Vite plugin that enables the compound components pattern in Svelte projects.
Compound components allow you to create components with semantically related sub-components that work together. Instead of passing many props, you compose components together.
npm install @volpe/svelte-compound
# or
pnpm add @volpe/svelte-compound
# or
bun add @volpe/svelte-compoundAdd the plugin to your vite.config.js:
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { compoundComponents } from '@volpe/svelte-compound';
export default defineConfig({
plugins: [
svelte(),
compoundComponents()
]
});Create a directory structure like this:
src/components/Card/
├── index.svelte → Main Card component
├── _header.svelte → Card._header subcomponent
└── _body.svelte → Card._body subcomponent
Naming rules:
- Subcomponents MUST start with
_(underscore) - Name after
_must be camelCase (e.g.,_header.svelte,_body.svelte) - Components must be inside a
components/directory
Example index.svelte:
<script lang="ts">
import type { Snippet } from "svelte"
const { children }: { children?: Snippet } = $props()
</script>
<div class="card">
{#if children}
{@render children()}
{/if}
</div>
<style>
.card {
border: 1px solid #ccc;
padding: 1rem;
border-radius: 4px;
}
</style>Example _header.svelte:
<script lang="ts">
import type { Snippet } from "svelte"
const { children }: { children?: Snippet } = $props()
</script>
<div class="card-header">
{#if children}
{@render children()}
{/if}
</div>
<style>
.card-header {
font-weight: bold;
border-bottom: 1px solid #eee;
padding-bottom: 0.5rem;
}
</style><script>
import Card from '$lib/components/Card';
</script>
<Card>
<Card._header>My Card Title</Card._header>
<Card._body>Card content goes here</Card._body>
</Card>| Option | Type | Default | Description |
|---|---|---|---|
exclude |
string[] |
['routes'] |
Directories to exclude from scanning |
Example:
compoundComponents({
exclude: ['routes', 'admin']
})The plugin:
- Scans for
components/directories within yourrootPath - Detects folders containing
index.svelteand_*.sveltesubcomponents - Generates an
index.tsthat exports the main component with subcomponents attached - Uses
Object.assign()to attach subcomponents to the main component
Generated index.ts example:
// Auto-generated by vite-plugin-svelte-compound-components
// Do not edit this file manually
import _header from './_header.svelte';
import _body from './_body.svelte';
import root from './index.svelte';
export default Object.assign(root, {
_header,
_body
});TypeScript types work automatically. When you use Card._header, TypeScript will infer the correct component type.
src/components/Tabs/
├── index.svelte → <Tabs>
├── _tab.svelte → <Tabs._tab>
└── _panel.svelte → <Tabs._panel>
Usage:
<Tabs>
<Tabs._tab>Tab 1</Tabs._tab>
<Tabs._tab>Tab 2</Tabs._tab>
<Tabs._panel>Content 1</Tabs._panel>
<Tabs._panel>Content 2</Tabs._panel>
</Tabs>src/components/Menu/
├── index.svelte
├── _item.svelte
├── _divider.svelte
└── _group.svelte
Usage:
<Menu>
<Menu._item>New File</Menu._item>
<Menu._item>Open</Menu._item>
<Menu._divider />
<Menu._group title="Recent">
<Menu._item>file1.txt</Menu._item>
<Menu._item>file2.txt</Menu._item>
</Menu._group>
</Menu>- Hot Module Replacement (HMR) support
- TypeScript support
- Works with SvelteKit and standalone Svelte + Vite
- Zero runtime overhead
- Auto-generates index.svelte and index.ts for new component folders
- Recursive scanning of all
components/directories
- Only one level of nesting (e.g.,
Card._headerworks, but notCard._header._title) - Subcomponents must be in the same directory as the parent
index.svelte - Subcomponent names must follow
_camelCasepattern
MIT