A fully accessible, animated accordion component built with Svelte 5 using the Context API and the Compound Component pattern.
This project demonstrates how to build reusable, composable UI components in Svelte 5 using the Compound Component pattern. Instead of passing configuration through props, compound components share implicit state through Svelte's Context API, enabling a clean and flexible API.
setContext and getContext for clean state sharingAccordion, AccordionItem, AccordionTrigger, and AccordionContentAccordion/
├── Accordion.svelte # Root component, provides context
├── AccordionItem.svelte # Container for each item
├── AccordionTrigger.svelte # Clickable header/button
├── AccordionContent.svelte # Collapsible content panel
├── accordion-context.svelte.ts # Context creation and retrieval
├── types.ts # TypeScript interfaces
└── index.ts # Public exports
<script>
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger
} from '$lib/components/accordion';
</script>
<Accordion single defaultExpanded={['item-1']}>
<AccordionItem id="item-1">
<AccordionTrigger itemId="item-1">What is your return policy?</AccordionTrigger>
<AccordionContent itemId="item-1">
<p>We offer a 30-day return policy for all unused items.</p>
</AccordionContent>
</AccordionItem>
<AccordionItem id="item-2">
<AccordionTrigger itemId="item-2">How long does shipping take?</AccordionTrigger>
<AccordionContent itemId="item-2">
<p>Standard shipping takes 5-7 business days.</p>
</AccordionContent>
</AccordionItem>
</Accordion>
<Accordion>| Prop | Type | Default | Description |
|---|---|---|---|
single |
boolean |
false |
Only allow one item open at a time |
defaultExpanded |
string[] |
[] |
Initially expanded item IDs |
onExpandedChange |
(ids: string[]) => void |
– | Callback when expanded items change |
class |
string |
'' |
Additional CSS class |
<AccordionItem>| Prop | Type | Description |
|---|---|---|
id |
string |
Unique identifier for this item |
class |
string |
Additional CSS class |
<AccordionTrigger> / <AccordionContent>| Prop | Type | Description |
|---|---|---|
itemId |
string |
ID of the accordion item this belongs to |
class |
string |
Additional CSS class |
The accordion uses Svelte 5's Context API to share state between components without prop drilling:
// accordion-context.svelte.ts
import { setContext, getContext } from 'svelte';
import { SvelteSet } from 'svelte/reactivity';
const ACCORDION_KEY = Symbol('accordion');
export function createAccordionContext(options) {
const expandedIds = new SvelteSet(options.defaultExpanded);
const context = {
expandedIds,
toggle(id) {
/* ... */
},
isExpanded(id) {
return expandedIds.has(id);
}
// ...
};
return setContext(ACCORDION_KEY, context);
}
export function getAccordionContext() {
return getContext(ACCORDION_KEY);
}
Child components retrieve the context and react to state changes:
// In AccordionTrigger.svelte
const accordion = getAccordionContext();
let isExpanded = $derived(accordion.isExpanded(itemId));
pnpm install
pnpm dev
pnpm build
Made with ❤️ using Svelte 5, SvelteKit, and TypeScript
MIT